我正在开发一个内部备份程序,该程序利用.NET 4.5的ZipArchive
和ZipFile
类。到目前为止,我已经成功地压缩了不同大小和内容的文件夹,但当压缩一个大文件夹时,UI会冻结,直到所有备份完成。我一直在谷歌和SO上寻找解决方案,但到目前为止运气不佳。
这是我启动后台线程的调用:
Dispatcher.Invoke(() =>
{
var result = BackupFolder(b1.Tag.ToString(), b1.Name);
Log = result.Result ? new BackupLog(string.Format("Finished zipping '{0}.zip'", b1.Name)) : new BackupLog(string.Format("Error zipping '{0}.zip'", b1.Name));
}, DispatcherPriority.Background);
这是它调用的方法:
private async Task<bool> BackupFolder(string path, string folderName)
{
try
{
var zipPath = string.Format("{0}{1}.zip", _backupPath.Backslash(), folderName);
Log = new BackupLog(string.Format("Starting zipping '{0}.zip'", folderName));
if (File.Exists(zipPath)) // ZIP already exists, let's update the contents
{
using (var existingZip = new FileStream(zipPath, FileMode.Open))
{
using (var archive = new ZipArchive(existingZip, ZipArchiveMode.Update))
{
foreach (var file in Directory.GetFiles(path))
{
if (archive.Entries.Any(a => a.FullName.Equals(Path.GetFileName(file)))) // Check if current file exists in the ZIP
{
var zipArchiveEntry =
archive.Entries.First(a => a.FullName.Equals(Path.GetFileName(file)));
if (zipArchiveEntry != null) zipArchiveEntry.Delete(); // Remove existing file from ZIP
}
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}
}
else // ZIP does not exist, let's create it
{
ZipFile.CreateFromDirectory(path, zipPath);
}
return await Task.FromResult(true);
}
catch (Exception ex)
{
Debugger.Break();
}
return await Task.FromResult(false);
}
我尝试过使用Task.Run()
,创建BackgroundWorker
,可能还有其他一些我忘记了的事情,试图让UI在后台快速移动时保持交互式。
有什么建议吗?谢谢
您的BackupFolder()
方法是"AINO":async
仅在名称上。async
关键字的存在是为了更改方法的语义,使其可以使用await
关键字。您唯一使用await
的地方是,您正在"等待"一个初始化处于完成状态的Task
对象。没有实际的异步等待。
同时,您正在使用Dispatcher.Invoke()
来执行该方法。这意味着您明确指示框架在UI线程中执行您的方法。这一点,再加上BackupFolder()
方法实际上不是异步的,意味着在操作期间会阻塞UI线程。
要修复代码,请将BackupFolder()
改回简单的同步方法:
private bool BackupFolder(string path, string folderName)
{
.
.
.
return true;
}
catch (Exception ex)
{
Debugger.Break();
}
return false;
}
然后,不调用Dispatcher.Invoke()
,只需在自己的Task
:中运行同步BackupFolder()
方法
string path = b1.Tag.ToString(), folderName = b1.Name;
bool result = await Task.Run(() => BackupFolder(path, folderName));
注:
- 我在上面是保守的,并假设
b1
对象,无论它是什么,都是而不是线程安全的。因此,我添加了用于从b1
对象捕获当前值的局部变量,这样,当BackupFolder()
方法最终在任务线程中执行时,这些值可以安全地传递给它 - 当然,在这里使用
await
运算符需要将包含该调用的方法标记为async
。这可能反过来要求该方法的调用者标记为async
,依此类推。async
就是这样
首先,Dispatcher.Invoke(...);
或InvokeAsync
不会在后台线程中运行任何内容。Dispatcher类的目的恰恰相反——调度UI线程上的工作。
其次,您错误地使用了async/await。只有任务内部的内容才会在后台线程中运行:
await DoSomething();
async Task DoSomething(){
Trace.WriteLine("This is still in UI thread")
await Task.Run(...); //action inside task will run asynchronously
Trace.WriteLine("This is UI thread again");
}
所以你的代码可以看起来像这样:
//this is UI thread
bool result = await Task.Run(() => BackupFolder(b1.Tag.ToString(), b1.Name"));
//this is UI thread again
private bool BackupFolder(string path, string folderName){
//this is background thread, since it is invoked from Task;
}