サイトアイコン CAD日記

C#でzip圧縮と解凍したりasync/awaitで非同期処理をするソースコード公開

zipcopyという名の開発プロジェクトはここらで終わりにしようと思い至ったので、そのソースコードを公開する。
zipcopyのソースコード
開発環境はVisualStudio 2017 C#で、NuGetでDotNetZipを組み込んだプロジェクト一式。
DotNetZipは一応組み込んでみただけで、メインは別口でインストールした7zipのプログラムを呼び出して使うほうだ。

ただ公開してもつまらないので、ポイントを以下で明らかにする。
まずはzipcopyがどんなアプリなのか。

コピー元のフォルダを選択、コピー先のフォルダを選択して、実行ってやれば、コピー元でzip圧縮した結果をコピー先にコピーしてくれるってもの。ネットワークのフォルダ共有の場合、圧縮してコピーしたほうが帯域制御になって、かつ速い場合もあるんじゃないかと思って作ったものだが、その当初の発想はかなりまちがっていたことがわかったので、その点は参考にならないかな。。

【ポイント1:7zipの圧縮/解凍はGUIからやるだけでなく、CUIのインターフェースが公開されている】
C:\Program Files\7-Zipの中にある7zG.exeがそうで、以下のコマンドラインを実行してやれば圧縮解凍できる。
7zG.exe a [圧縮後ファイル] [圧縮元フォルダ] ※詳細はCompress関数を参照
7zG.exe x -y -o [解凍先フォルダ] [解凍する圧縮ファイル] 詳細はExtract関数を参照
CompressおよびExtract関数内では、DotNetZipによる圧縮解凍もしているので参考にされたし。

// コピー元でzip圧縮
private bool Compress(ref string strZip)
{
	strZip = uty.GetZipName(opt.m_strCopySrc);
	string detail = "";
	bool stat = true;
	Stopwatch sw = uty.timeStart();
	DateTime dt = DateTime.Now;
	try
	{
		if (opt.m_i7zipUse == 1)
		{
			string param1 = "\"" + strZip + "\"";
			string param2 = "\"" + opt.m_strCopySrc + "\"";
			Process p = Process.Start(opt.m_str7zipPath2, "a " + param1 + " " + param2);
			p.WaitForExit();
			if(p.ExitCode != 0)  // 失敗
			{
				p.Dispose();
				throw new FormatException("zip圧縮に失敗");
			}
			p.Dispose();
		}
		else
		{
			ZipFile zip = new ZipFile(Encoding.GetEncoding("shift_jis"));
			zip.CompressionLevel = CompressionLevel.BestCompression;
			if (Environment.Is64BitProcess)
				zip.UseZip64WhenSaving = Zip64Option.AsNecessary;
			if (uty.IsSrcFolder(opt.m_strCopySrc))
			{
				string strFolder = Path.GetFileName(opt.m_strCopySrc);
				zip.AddDirectory(opt.m_strCopySrc, strFolder);
			}
			else
				zip.AddFile(opt.m_strCopySrc, "");
			zip.Save(strZip);
			zip.Dispose();
		}
	}
	catch (Exception ex)
	{
		stat = false;
		detail = string.Format("例外エラー({0})", ex.Message);
	}
	finally
	{
		Invoke(new logInfo(addLogDelegate), dt, "コピー元で圧縮", opt.m_strCopySrc, opt.m_strCopyDst, stat, uty.timeEnd(sw), detail);
	}
	return stat;
}

// コピー先でzip解凍
private bool Extract(string dstFull)
{
	string detail = "";
	bool stat = true;
	Stopwatch sw = uty.timeStart();
	DateTime dt = DateTime.Now;
	try
	{
		if (opt.m_i7zipUse == 1)
		{
			string param1 = "\"" + opt.m_strCopyDst + "\"";
			string param2 = "\"" + dstFull + "\"";
			Process p = Process.Start(opt.m_str7zipPath2, "x -y -o" + param1 + " " + param2);
			p.WaitForExit();
			if (p.ExitCode != 0)  // 失敗
			{
				p.Dispose();
				throw new FormatException("zip解答に失敗");
			}
			p.Dispose();
		}
		else
		{
			var enc = new ReadOptions() { Encoding = Encoding.GetEncoding("shift_jis") };
			ZipFile zip = ZipFile.Read(dstFull, enc);
			zip.ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently;
			zip.ExtractAll(opt.m_strCopyDst);
			zip.Dispose();
		}
	}
	catch (Exception ex)
	{
		stat = false;
		detail = string.Format("例外エラー({0})", ex.Message);
	}
	finally
	{
		Invoke(new logInfo(addLogDelegate), dt, "コピー先で解凍", opt.m_strCopySrc, opt.m_strCopyDst, stat, uty.timeEnd(sw), detail);
	}
	return stat;
}
</pre>

【ポイント2:時間のかかる処理はasync/awaitで非同期とすべし】
同期処理で時間がかかる処理(10秒以上)をやらせると、自分が応答なしの状態になるので気持ち悪い。非同期で時間がかかる処理をやらせておいて、自分はクローズできない状態に制御しておけば、GUI操作で自分と呼び出し先の処理を行ったり来たりできていい感じになる。以下、実行ボタンを押したときの処理。asyncとawaitの使い方の参考になるかな。
<pre class="brush: c-sharp;">private async void button3_Click(object sender, EventArgs e)
{
	DateTime dt = DateTime.Now;
	UpdateOption();
	string errMsg = "";
	if (!PreCheck(ref errMsg))
	{
		Invoke(new logInfo(addLogDelegate), dt, "事前チェック", opt.m_strCopySrc, opt.m_strCopyDst, false, 0, errMsg);
		return;
	}

	bool stat = false;
	button3.Enabled = false;
	this.Text += " 【実行中】";
	exec = true;
	try
	{
		await Task.Run(() =>
		{
			string strZip = "";
			if (Compress(ref strZip))
			{
				string dstFull = "";
				if (CopyFile(strZip, ref dstFull))
				{
					stat = true;
					if (opt.m_iSrcZipDel == 1)
						stat = uty.FileDelete(listView1, strZip, opt.m_iDelTimeout);
					if (opt.m_iDstZipOpen == 1 && Extract(dstFull))
					{
						if (opt.m_iDstZipDel == 1)
							stat = uty.FileDelete(listView1, dstFull, opt.m_iDelTimeout);
					}
				}
			}
		});
	}
	catch (Exception)
	{
		...

呼び出し先から呼び出し元のコントロールをいじる際は注意が必要で、delegate宣言した関数としておく。以下は、圧縮などの実行経過を自分のリストビューにログとして追加する処理。

delegate void logInfo(DateTime dt, string operation, string src, string dst, bool stat, double time, string detail);
public void addLogDelegate(DateTime dt, string operation, string src, string dst, bool stat, double time, string detail)
{
	ListViewItem lvi;
	lvi = listView1.Items.Add(dt.ToString());
	lvi.SubItems.Add(time.ToString());
	lvi.SubItems.Add(operation);
	lvi.SubItems.Add(src);
	lvi.SubItems.Add(dst);
	lvi.SubItems.Add(stat ? "成功" : "失敗");
	lvi.SubItems.Add(detail);
	listView1.EnsureVisible(lvi.Index);
	listView1.Update();
}

その他ポイントは、以下に箇条書きで書いておくので、詳細はソースコードの中から見つけてくれ。カッコ内は関数名。
・zipのように巨大なファイルは削除できるまでに時間がかかる場合があるので、少し時間をおいてからリトライすべし(FileDelete)
・処理時間を計測するならStopwatchでStart&Stopすべし(timeStart、timeEnd)
・エクスプローラの右クリックメニュに追加・削除するのはレジストリ操作で可能(button5_Click、button6_Click)
・ファイル選択でフォルダ選択することだってできるのよ(button1_Click)
・ファイルコピーは、コピー中のGUI画面を出すことができるのさ(CopyFile)

モバイルバージョンを終了