2016-01-06 142 views
1

我必須處理一些使用異步函數時不能正確運行的舊東西,並且由於我對此概念的理解有限,因此我一直在努力尋找處理以下問題的最佳方法問題。異步/等待方法和異常處理的最佳做法

我有一個按鈕,單擊它時會執行長時間運行的作業(解壓大型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投擲在BackupUnzip方法永遠不會起泡,並沒有抓住我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線程處理作業一樣。任何暗示什麼是錯的?

+0

你還在使用'Task.Run'嗎? – i3arnon

+2

看起來你標記了你的方法'async',但是讓它們同步,而不是異步工作,但是由於你沒有看到它們的實現,所以沒有辦法說出具體是什麼,特別是你做錯了。 – Servy

+0

我使用方法實現編輯了我的帖子。不,我不再使用'Task.Run'。 –

回答

-1

我會建議你async-await設置如下:

try 
{ 
    await Backup(tokenSource.Token, progress); 
    await Unzip(tokenSource.Token, progress); 
} 
catch (Exception ex) 
{ 
    // your exceptions from the async tasks will end up here 
} 

而且方法是這樣定義的:

private async Task Backup(CancellationToken token, IProgress<ProgressReport> progress) 
{ 
    await Task.Run(() => 
    { 
     // do cpu bound work here (async) 
    }); 
} 

private async Task Unzip(CancellationToken token, IProgress<ProgressReport> progress) 
{ 
    await Task.Run(() => 
    { 
     // do cpu bound work here (async) 
    }); 
} 

例外將現在總是在主嘗試{}趕上最後

欲瞭解更多關於async-await的信息,我隨時諮詢Stephen Clary的博客:http://blog.stephencleary.com/2012/02/async-and-await.html

-2

感謝@ i3arnon的評論,我設法達到了我想要的。 這裏是我的電話是如何改變:

await Task.Run(async() => 
{ 
    await Backup(tokenSource.Token, progress); 
    await Unzip(tokenSource.Token, progress); 
}); 

而且兩者的功能仍然宣佈private async Task

這樣,由於Task.Run,作業在背景上運行,所以UI不會凍結,異常會正常冒泡。