我必須處理一些使用異步函數時不能正確運行的舊東西,並且由於我對此概念的理解有限,因此我一直在努力尋找處理以下問題的最佳方法問題。異步/等待方法和異常處理的最佳做法
我有一個按鈕,單擊它時會執行長時間運行的作業(解壓大型ZIP壓縮文件,需要幾分鐘)。它Command
執行方法的定義如下:
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;
}
}
}
此時,異步任務是工作的罰款,用戶界面不凍結,但問題是Exception
投擲在Backup
和Unzip
方法永遠不會起泡,並沒有抓住我n 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'嗎? – i3arnon
看起來你標記了你的方法'async',但是讓它們同步,而不是異步工作,但是由於你沒有看到它們的實現,所以沒有辦法說出具體是什麼,特別是你做錯了。 – Servy
我使用方法實現編輯了我的帖子。不,我不再使用'Task.Run'。 –