【发布时间】:2016-01-07 19:22:08
【问题描述】:
我必须处理一些使用异步函数无法正常运行的旧东西,并且由于我对这个概念的理解有限,我正在努力寻找处理以下问题的最佳方法。
我有一个按钮,单击该按钮将执行长时间运行的工作(解压缩大型 ZIP 存档,需要几分钟)。它的Commandexecute 方法是这样定义的:
private async void Import()
{
// some stuff
tokenSource = new CancellationTokenSource();
IProgress<ProgressReport> progress = new Progress<ProgressReport>(data => Report(data));
try
{
await Task.Run(() =>
{
Backup(tokenSource.Token, progress);
Unzip(tokenSource.Token, progress);
});
}
catch(Exception)
{
// do some rollback operation
}
以及像这样定义的等待函数:
private void Backup(CancellationToken token, IProgress<ProgressReport> progress)
{
token.ThrowIfCancellationRequested();
var parent = Path.GetFullPath(Path.Combine(Paths.DataDirectory, ".."));
if (!Directory.Exists(parent))
{
progress.Report(new ProgressReport(Level.Info, string.Format(
"Root Directory ({0}) does not exist; Creating it.", parent)));
Directory.CreateDirectory(parent);
return;
}
if (!Directory.Exists(Paths.DataDirectory))
{
progress.Report(new ProgressReport(Level.Info, string.Format(
"Data Directory ({0}) does not exist; nothing to backup.", Paths.DataDirectory)));
return;
}
// Generate a name for the backup
try
{
progress.Report(new ProgressReport(Level.Info, string.Format(
"Renaming source Data Directory ({0}) to a temporary name.", Paths.DataDirectory)));
var temp = Path.Combine(parent, Guid.NewGuid().ToString("N"));
Directory.Move(Paths.DataDirectory, temp);
// Success, let's store the backupFolder in a class field
backupFolder = temp;
progress.Report(new ProgressReport(Level.Info, string.Format(
"Source Data Directory ({0}) successfully renamed to {1}.", Paths.DataDirectory, backupFolder)));
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
progress.Report(new ProgressReport(Level.Warn, "Cancelling Import Operation."));
throw;
}
catch (Exception ex)
{
// some stuff then throw Exception to bubble-up
throw;
}
}
和
private async Task Unzip(CancellationToken token, IProgress<ProgressReport> progress)
{
try
{
token.ThrowIfCancellationRequested();
var parent = Path.GetFullPath(Path.Combine(Paths.DataDirectory, ".."));
try
{
progress.Report(new ProgressReport(
Level.Info, string.Format("Uncompressing export file {0}.", InputFileName)));
using (var zipArchive = ZipFile.Open(InputFileName, ZipArchiveMode.Read, Encoding.UTF8))
{
var extractedCount = 0;
var totalCount = zipArchive.Entries.Count;
foreach (var entry in zipArchive.Entries)
{
progress.Report(new ProgressReport(
Level.Off, string.Format("Extracting {0}, {1}/{2}",
entry.FullName, extractedCount + 1, totalCount), extractedCount, totalCount));
if (string.IsNullOrEmpty(entry.Name) && string.IsNullOrEmpty(entry.FullName))
continue;
if (string.IsNullOrEmpty(entry.Name))
{
var dir = Path.Combine(parent, entry.FullName);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
else entry.ExtractToFile(Path.Combine(parent, entry.FullName), true);
notPaused.WaitOne();
token.ThrowIfCancellationRequested();
extractedCount++;
}
// Everything completed, update the progress bar.
progress.Report(new ProgressReport(totalCount, totalCount));
}
}
catch (OperationCanceledException)
{
progress.Report(new ProgressReport(Level.Warn, "Cancelling Import Operation."));
throw;
}
catch (Exception ex)
{
// Some stuff then throw Exception to bubble-up
throw;
}
}
}
此时,异步作业运行良好,UI 没有冻结,但问题是 Backup 和 Unzip 中抛出的 Exception 方法永远不会冒泡,也不会在 Import 中捕获方法,因此程序在throw 指令处崩溃。
经过一番研究,我在this msdn article 中发现这是使用void 返回方法时的正常行为。所以我稍微改变了程序,现在使用await 的调用是这样的:
try
{
await Backup(tokenSource.Token, progress);
await Unzip(tokenSource.Token, progress);
}
我的方法是这样定义的:
private async Task Backup(CancellationToken token, IProgress<ProgressReport> progress)
{
// Same logic
Task.Delay(1000);
}
private async Task Unzip(CancellationToken token, IProgress<ProgressReport> progress)
{
// Same logic
Task.Delay(1000);
}
现在,Import 方法会出现异常,但 UI 在整个作业完成时间内都被冻结,就像作业由 UI 线程处理一样。有什么问题的提示吗?
【问题讨论】:
-
你还在用
Task.Run吗? -
看起来你标记了你的方法
async,但是让它们同步工作,而不是异步工作,但由于你没有展示它们的实现,所以没有办法说具体是什么,你做错了。 -
我用方法实现编辑了我的帖子。不,我不再使用
Task.Run。 -
@BastienM。如果你想让这些方法在后台运行,你需要
Task.Run -
@BastienM。这是无关的。您可以使用
Task.Run。在它内部调用一个异步方法并等待返回的任务。在这种情况下,异常会“冒泡”。
标签: c# wpf asynchronous exception-handling async-await