没有什么能像完成一半的任务那样致命。
在接下来的系列文章中,我将介绍C#语言的新功能之一-Async / Await。我将从破坏与该编码模式通常相关的两个神话开始。
- 任务与线程不同。
调用异步方法并不自动意味着将创建一个新线程。许多IO方法使用低级I / O请求数据包和中断来管理操作,而无需新线程。有关更多信息,请参见Stephen Cleary的博客文章。但是,异步方法可能在与其调用的线程不同的线程上返回。这对于WPF之类的平台尤其重要,在该平台上,对控件或其他类(例如ObservableCollection)的所有更新都必须在创建(UI)线程上完成。我在先前的文章中讨论过的诸如perDispatcherHelper之类的实用程序将对此有所帮助。 - 使用异步方法不会自动提高应用程序性能。
使方法异步不会使其运行更快。例如,如果要从磁盘加载文件或从Web URL下载数据,则同步和异步方法将花费相同的时间。使用异步方法的主要原因是避免阻塞UI线程,从而为您的应用程序用户创造更好的体验。但是,如果您要执行多个异步操作,则可以并行运行它们将有一些好处–我将在以后的文章中对此进行详细介绍。
本文的演示项目展示了基本的异步/等待操作,并介绍了用于任务管理的有用的帮助程序类。async / await的核心用法是要执行的工作块,而不会阻塞UI线程。在这种情况下,DemoWorker.DoWork()将计入指定的步骤数,在每个步骤之间暂停并向调用者提供持续的反馈。作为对助手类的测试,DoWork()在到达步骤8时将引发异常(这仅适用于Task1实例)。
public static class DemoWorker
{
// Simulate a long running task
// Repeat tickCount times, pausing by tickInterval each loop, and providing feedback to the caller via progress
public static async Task<string> DoWork(string workerId, int tickCount, TimeSpan tickInterval, IProgress<int> progress, CancellationToken token)
{
var i = 1;
while (i <= tickCount && !token.IsCancellationRequested)
{
await Task.Delay(tickInterval, token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
progress.Report(i++);
// test what happens when an exception is thrown inside the task
if (!token.IsCancellationRequested && i > 8)
throw new ApplicationException("Worker " + workerId + ": Bang!!!");
}
return token.IsCancellationRequested
? string.Empty
: "Result from worker " + workerId;
}
}
- 暂停是使用Task.Delay()生成的-绝对不要在异步方法中使用Thread.Sleep()。
- 在库类中,每次使用await时,都应使用.ConfigureAwait(false)标记任务。这样可以防止在任务执行时捕获和阻止UI上下文。
- 使用IProgress <T>实现处理对调用者的反馈。.net框架将自动将所有进度调用编组回UI线程。
- CancellationToken为调用者提供了一种线程安全的方式来取消正在运行的任务。
MainViewModel生成三个具有不同参数的DemoWorker实例,并使用Task.WhenAll()并行运行它们。您应该避免使用Task.WaitAll()和其他无法等待的类似阻止方法。这些任务中的每一个都有其自己的IProgress <int>实现,该实现将更新适当的属性和绑定的UI控件。每个任务都有一个延续,如果任务完成就可以立即更新UI,而无需等待Task.WhenAll()调用完成。
perTaskHelper包含许多方法来管理任务的操作-根据需要处理超时/取消/异常,并返回状态值。它们的响应类型的命名与标准C#委托类相似-操作仅执行一个操作并返回状态,函数执行一个将数据值与状态一起返回的操作。将捕获任务中引发的任何异常,无论任务的结果如何,都将提供单个返回状态。
注意如何将相同的CancellationTokenSource或其令牌用于这三个任务以及对Task.WhenAll(...)。ExecuteActionWithTimeoutAsync(...)的调用。如果整个操作被取消或超时,则允许取消单个品尝。
运行演示应用程序,并记下各种超时设置以及单击“取消”按钮时的行为。该GlobalStatus字符串可能看起来有些奇怪,显示“已完成OK”,即使任务1抛出其DemoWorker内的异常。但是,这只是显示Task.WhenAll()调用的响应,如果没有取消或超时,它将始终为OK。
请注意如何在Task.WhenAll()完成之后再次等待这三个任务。这被认为是最佳实践–在某些情况下,即使对已完成的任务,使用Task.Result也会阻塞。