2016-01-06 140 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不会冻结,异常会正常冒泡。