当前位置:首页 > C#编程 > C#/.net框架 > 正文内容

C# 异步和等待,async/await

Jorge2年前 (2022-05-08)C#/.net框架1659

 

首先,重点是:异步将从根本上改变大多数代码的编写方式。

是的,我相信 async/await 会比 LINQ 产生更大的影响。理解异步将在短短几年内成为基本必需品。

关键字介绍

让我们直接开始吧。我将使用一些稍后将阐述的概念——请继续阅读第一部分。

异步方法看起来像这样:



public async Task DoSomethingAsync() 
{
   // In the Real World, we would actually do something... 
  // For this example, we're just going to (asynchronously) wait 100ms.   
   await Task.Delay(100);
 }

 

 


“async”关键字在该方法中启用“await”关键字并更改方法结果的处理方式。这就是 async 关键字所做的一切!它不会在线程池线程上运行此方法,也不会执行任何其他类型的魔术。async 关键字启用 await 关键字(并管理方法结果)。

异步方法的开头就像任何其他方法一样执行。也就是说,它会同步运行,直到遇到“await”(或抛出异常)。

“await”关键字是事情可以异步的地方。Await 就像一个一元运算符:它接受一个参数,一个awaitable(一个“awaitable”是一个异步操作)。Await 会检查 awaitable 是否已经完成;如果 awaitable 已经完成,则该方法将继续运行(同步,就像常规方法一样)。

如果“await”看到awaitable 尚未完成,则它会异步执行。它告诉 awaitable 在完成时运行该方法的其余部分,然后从异步方法返回

稍后,当等待完成时,它将执行异步方法的其余部分。如果您正在等待内置的可等待对象(例如任务),则异步方法的其余部分将在“await”返回之前捕获的“上下文”上执行。

我喜欢将“await”视为“异步等待”。也就是说, async方法会暂停,直到 awaitable 完成(因此它等待),但实际线程并未被阻塞(因此它是异步的)。

待办事项

正如我提到的,“await”接受一个参数——一个“awaitable”——这是一个异步操作。.NET 框架中已经有两种常见的可等待类型:Task<T> 和 Task。

还有其他可等待类型:诸如“Task.Yield”之类的特殊方法返回不是任务的可等待对象,并且 WinRT 运行时(在 Windows 8 中推出)具有非托管的可等待类型。您还可以创建自己的可等待(通常是出于性能原因),或使用扩展方法使不可等待类型成为可等待类型。

这就是我要说的关于制作自己的可等待对象的全部内容。在使用 async/await 的整个过程中,我只需要编写几个可等待对象。如果您想了解更多关于编写自己的可等待对象的信息,请参阅Parallel Team 博客Jon Skeet 的博客

关于 awaitables 的一个重要点是:它是可等待类型,而不是返回类型的方法。换句话说,您可以等待返回 Task 的异步方法的结果……因为该方法返回 Task,而不是因为它是 async因此,您还可以等待返回 Task非异步方法的结果


public async Task NewStuffAsync()
{
  // Use await and have fun with the new stuff.
  await ...
}

public Task MyOldTaskParallelLibraryCode()
{
  // Note that this is not an async method, so we can't use await in here.
  ...
}

public async Task ComposeAsync()
{
  // We can await Tasks, regardless of where they come from.
  await NewStuffAsync();
  await MyOldTaskParallelLibraryCode();
}

 

 



提示:如果您有一个非常简单的异步方法,您可以在不使用 await 关键字的情况下编写它(例如,从另一个方法返回一个任务)。但是,请注意,在省略asyncawait存在陷阱

返回类型

异步方法可以返回 Task<T>、Task 或 void。在几乎所有情况下,您都希望返回 Task<T> 或 Task,并且仅在必要时返回 void。

为什么要返回 Task<T> 或 Task?因为它们是可等待的,而 void 不是。因此,如果您有一个异步方法返回 Task<T> 或 Task,那么您可以将结果传递给 await。使用 void 方法,您没有任何东西可以传递给 await。

当您有异步事件处理程序时,您必须返回 void。


您还可以将 async void 用于其他“顶级”类型的操作 - 例如,控制台程序的单个“静态 async void MainAsync()”。但是,这种使用 async void 有其自身的问题;请参阅异步控制台程序异步 void 方法的主要用例是事件处理程序。

返回值

返回 Task 或 void 的异步方法没有返回值。返回 Task<T> 的异步方法必须返回 T 类型的值:


public async Task<int> CalculateAnswer()
{
  await Task.Delay(100); // (Probably should be longer...)

  // Return a type of "int", not "Task<int>"
  return 42;
}

 

 


习惯这有点奇怪,但这种设计背后很好的理由

语境

在概述中,我提到当您等待内置的可等待对象时,等待对象将捕获当前的“上下文”,然后将其应用于异步方法的其余部分。这个“上下文”究竟是什么?

简单回答:

  1. 如果您在 UI 线程上,那么它就是 UI 上下文。

  2. 如果您正在响应 ASP.NET 请求,则它是一个 ASP.NET 请求上下文。

  3. 否则,它通常是一个线程池上下文。

复杂的答案:

  1. 如果 SynchronizationContext.Current 不为 null,则它是当前 SynchronizationContext。(UI 和 ASP.NET 请求上下文是 SynchronizationContext 上下文)。

  2. 否则,它是当前的 TaskScheduler(TaskScheduler.Default 是线程池上下文)。

这在现实世界中意味着什么?一方面,捕获(和恢复)UI/ASP.NET 上下文是透明的:


// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the UI thread is not blocked by the file download.
  await DownloadFileAsync(fileNameTextBox.Text);

  // Since we resume on the UI context, we can directly access UI elements.
  resultTextBox.Text = "File downloaded!";
}

// ASP.NET example
protected async void MyButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the ASP.NET thread is not blocked by the file download.
  // This allows the thread to handle other requests while we're waiting.
  await DownloadFileAsync(...);

  // Since we resume on the ASP.NET context, we can access the current request.
  // We may actually be on another *thread*, but we have the same ASP.NET request context.
  Response.Write("File downloaded!");
}

 

 


这对于事件处理程序来说非常有用,但结果证明它不是您想要的大多数其他代码(实际上,您将编写的大多数异步代码)。

避免上下文

大多数情况下,您不需要同步回“主”上下文。大多数异步方法在设计时都会考虑到组合:它们等待其他操作,每个方法都代表一个异步操作本身(可以由其他操作组合)。在这种情况下,您希望通过调用ConfigureAwait并传递 false来告诉等待者不要捕获当前上下文,例如:


private async Task DownloadFileAsync(string fileName)
{
  // Use HttpClient or whatever to download the file contents.
  var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false);

  // Note that because of the ConfigureAwait(false), we are not on the original context here.
  // Instead, we're running on the thread pool.

  // Write the file contents out to a disk file.
  await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);

  // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice.
}

// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the UI thread is not blocked by the file download.
  await DownloadFileAsync(fileNameTextBox.Text);

  // Since we resume on the UI context, we can directly access UI elements.
  resultTextBox.Text = "File downloaded!";
}

 

 


这个例子需要注意的重要一点是异步方法调用的每个“级别”都有自己的上下文。DownloadFileButton_Click 在 UI 上下文中启动,并调用 DownloadFileAsync。DownloadFileAsync 也在 UI 上下文中启动,但随后通过调用 ConfigureAwait(false) 退出其上下文。DownloadFileAsync 的其余部分在线程池上下文中运行。但是,当 DownloadFileAsync 完成并且 DownloadFileButton_Click 恢复时,它在 UI 上下文恢复。

一个好的经验法则是使用 ConfigureAwait(false) 除非您知道您确实需要上下文。

异步组合

到目前为止,我们只考虑了串行组合:一个异步方法一次等待一个操作。也可以启动多个操作并等待其中一个(或全部)完成。您可以通过启动操作来完成此操作,但不要等到稍后执行:

public async Task DoOperationsConcurrentlyAsync()
{
  Task[] tasks = new Task[3];
  tasks[0] = DoOperation0Async();
  tasks[1] = DoOperation1Async();
  tasks[2] = DoOperation2Async();

  // At this point, all three tasks are running at the same time.

  // Now, we await them all.
  await Task.WhenAll(tasks);
}

public async Task<int> GetFirstToRespondAsync()
{
  // Call two web services; take the first response.
  Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() };

  // Await for the first one to respond.
  Task<int> firstTask = await Task.WhenAny(tasks);

  // Return the result.
  return await firstTask;
}

通过使用并发组合(Task.WhenAll 或 Task.WhenAny),您可以执行简单的并发操作。您还可以将这些方法与 Task.Run 一起使用来进行简单的并行计算。但是,这不能替代任务并行库——任何高级 CPU 密集型并行操作都应该使用 TPL 来完成。

指南

阅读基于任务的异步模式 (TAP) 文档它写得非常好,包括 API 设计指南和 async/await 的正确使用(包括取消和进度报告)。

应该使用许多新的等待友好的技术来代替旧的阻塞技术。如果您的新异步代码中有这些旧示例中的任何一个,那么您就做错了 (TM):

老的新的描述
task.Waitawait task等待/等待任务完成
task.Resultawait task获取完成任务的结果
Task.WaitAnyawait Task.WhenAny等待/等待一组任务中的一个完成
任务.WaitAllawait Task.WhenAll等待/等待一组任务中的每一个完成
Thread.Sleepawait Task.Delay等待/等待一段时间
Task constructorTask.Run or TaskFactory.StartNew创建基于代码的任务

一个老外的博客转载的,继续涨下知识。

扫描二维码推送至手机访问。

版权声明:本文由7点博客发布,如需转载请注明出处。

本文链接:http://6dot.cn/?id=54

标签: .NET.NET框架
分享给朋友:

“C# 异步和等待,async/await” 的相关文章

C#的propertygrid控件,选择和修改项目时很慢

C#的propertygrid控件,选择和修改项目时很慢

C#的propertygrid控件是很强。可以实现类似Vitual Studio属性面板那样的效果。但是。。。。我们一直痛苦它在选择和修改项目的时候很慢。我的用法是使用控件的SelectObject来绑定数据。C# PgridMotionSpeed.SelectedObject ...

C# 当List.Find()返回一个结构体对象时,如何判断它为空?

C# 当List.Find()返回一个结构体对象时,如何判断它为空?

请看演示程序。我们知道List.Find() 如果找到则返回这个对象,找不到返回这个类型的缺省值。下面例子中,你无法这样写:            if(res1==null)        &nbs...

C# Modelbus crc16计算校验和程序

C# Modelbus crc16计算校验和程序

我们手里一个无刷电机,采用485的modelbus crc16协议来控制。因此需要一个计算校验和的工具。源码:C#using System;using System.Collections.Generic;using System.ComponentModel;usin...

C#中使用RegisterWindowMessage进行进程之间进行通讯

C#中使用RegisterWindowMessage进行进程之间进行通讯

注:大家都知道在window里,进程之间通讯可以用消息通讯。但是有个较冷门的知识就是RegisterWindowMessage。一般的sendmessage和postmessage是需要在窗体程序间进行通讯,你得知道对方窗口的句柄。这个句柄每次启动程序时是系统分配的,每次不一样。有了这个Regist...

谈谈ObservableCollection观察者集合

谈谈ObservableCollection观察者集合

注:事件很常见,但有重复的代码量。如果你想简化一下事件的编程,而且是整个程序只使用一个像事件池一样的东西,可以尝试使用下文讲的ObservableCollection观察者集合来做到。ObservableCollection<T>类表示一个动态数据集合,在添加项,移除项或刷新整个列表时,...

索引器(C# 编程指南)

索引器(C# 编程指南)

 索引器允许类或结构的实例就像数组一样进行索引。 无需显式指定类型或实例成员,即可设置或检索索引值。 索引器类似于属性,不同之处在于它们的访问器需要使用参数。以下示例定义了一个泛型类,其中包含用于赋值和检索值的简单 get 和 set 访问器方法。&...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。