C#–异步/等待(第1部分)

没有什么能像完成一半的任务那样致命。
 

在接下来的系列文章中,我将介绍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也会阻塞。

猜你喜欢

转载自blog.csdn.net/qq_28368039/article/details/107094688