编写异步函数

  • 对于任何异步函数,你可以使用Task替代void作为返回类型,让该方法称为更有效的异步(可以进行await)。 ```csharp async Task Main() { await PrintAnswerToLife(); }

// 这里方法会void也可以,但是返回void,Main并不能await async Task PrintAnswerToLife() { await Task.Delay(5000); int answer = 21 * 2; Console.WriteLine(answer); }

  1. - 并不需要在方法体中显示的返回Task。编译器会生成一个Task(当方法完成或发生异常时),这使得创建异步的调用链非常方便。
  2. ```csharp
  3. async Task Main()
  4. {
  5. await Go();
  6. }
  7. async Task Go()
  8. {
  9. await PrintAnswerToLife();
  10. Console.WriteLine("Done");
  11. }
  12. async Task PrintAnswerToLife()
  13. {
  14. await Task.Delay(5000);
  15. int answer = 21 * 2;
  16. Console.WriteLine(answer);
  17. }
  • 编译器会对返回Task的异步函数进行扩展,使其成为当发送信号或发生故障时使用TaskCompletionSource来创建Task的代码。(代码类似于下面) ```csharp async Task Main() { await Go(); }

async Task Go() { await PrintAnswerToLife(); Console.WriteLine(“Done”); }

//async Task PrintAnswerToLife() //{ // await Task.Delay(5000); // int answer = 21 * 2; // Console.WriteLine(answer); //}

Task PrintAnswerToLife() { var tcs = new TaskCompletionSource(); var awaiter = Task.Delay(5000).GetAwaiter(); awaiter.OnCompleted(() => { try { awaiter.GetResult(); int answer = 21 * 2; Console.WriteLine(answer); tcs.SetResult(null); } catch (Exception ex) { tcs.SetException(ex); } }); return tcs.Task; }


- 因此,当返回Task的异步方法结束的时候,执行就会跳回对它进行await的地方。(通过continuation)

<a name="Sxl3i"></a>
## 富客户端场景下

- 富客户端场景下,执行在此刻会跳回到UI线程(如果目前不在UI线程的话)。
- 否则,就是在continuation返回的任意线程上继续执行。
- 这意味着,在异步调用图中向上冒泡的似乎后,不会发生延迟成本,除非是UI线程启动的第一次“反弹”。

<a name="vWqlk"></a>
## 返回Task<TResult>

- 如果方法体返回TResult,那么异步方法就可以返回Task<TResult>
```csharp
async Task Main()
{
    await PrintAnswerToLife();
}

async Task<int> PrintAnswerToLife()
{
    await Task.Delay(5000);
    int answer = 21 * 2;
    Console.WriteLine(answer);
    return answer;
}
  • 其原理就是给TaskCompletion发送的信号带有值,而不是null
  • 与同步编程很相似,是故意这样设计的。

C#中如何设计异步函数

  • 以同步的方式编写方法。
  • 使用异步调用来代替同步调用,并且使用await。
  • 除了顶层方法外(UI空间的event handler),把你的方法的返回类型升级为Task或Task,这样它们就可以进行await了。

编译器能对异步函数生成Task意味什么?

  • 大多数情况下,你只需要在初始化IO-bound并发的底层方法里显示的初始化TaskCompletionSource,这种情况很少见。
  • 针对初始化compute-bound的并发方法,你可以使用Task.Run来创建Task。

异步调用图执行

  • 整个执行与之前同步例子中调用图执行的顺序一样,因为我们对每个异步函数的调用都进行了await。
  • 在调用图中创建了一个没有并行和重叠的连续流。
  • 每个await在执行中都创建了一个间隙,在间隙后,程序可以从终端出恢复执行。

异步Lambda表达式

  • 匿名方法(包括Lambda表达式),通过使用async也可以变成异步方法。
  • 调用方式也一样。 ```csharp async Task Main() { Func> unnamed = async() => {
      await Task.Delay(1000);
      return 10086;
    
    }; int result = await unnamed(); Console.WriteLine(result); }

```