C# Advanced Tutorial 1-15-AwaitAsync


主要内容概要

1 await/async语法和使用
2 原理探究和使用建议

Await Async

await/async 是C# 的保留关键字 ,.Net framework4.5 版本出现的,通常是成对出现。
async修饰方法,可以单独出现,但是有警告。
await在方法体,只能出现在task/async方法前面,只有await会报错。
await Task.Run()await task这种用法都是可以的。
.NetFramework4.5—-await/async 语法糖:由编译器提供的功能。
Common Language Runtime(CLR)

只有async没有await

只有async没有await,跟普通方法没有区别:

private static async void NoReturnNoAwait()
{
  //主线程执行
  Console.WriteLine($"NoReturnNoAwait Sleep before Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
  Task task = Task.Run(() =>//启动新线程完成任务
  {
    Console.WriteLine($"NoReturnNoAwait Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(1000);
    Console.WriteLine($"NoReturnNoAwait Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
  });

  //主线程执行
  Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}

async/await 一起用

主线程调用async/await方法,主线程遇到await返回执行后续动作;
await 后面的代码会等着task任务的完成后再继续执行。
例子一:
主线程的内容:

Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
NoReturn();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Console.Read();

子线程方法内容:

private static async void NoReturn()
{
  //主线程执行
  Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
  TaskFactory taskFactory = new TaskFactory();
  Task task = taskFactory.StartNew(() =>
  {
    Console.WriteLine($"NoReturn Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(3000);
    Console.WriteLine($"NoReturn Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
  });
  await task;//主线程到这里就返回了,执行主线程任务
  Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}

主线程在遇到await task这里的时候就返回了,await后面的动作就被阻塞了,但是主线程没有被阻塞,继续在工作。
更进一步的理解:这个await可以理解为continuewith,主线程启动task没有被阻塞,回去干自己的活。然后ContinueWith就是一个回调,这个时候的线程可能是主线程,也可能是一个新的线程。Await在这里的作用就类似于把await后面的代码包装成一个回调。
值得注意的是:回调的动作,可能是Task线程,也可能是新的子线程,也可能是主线程。

task.ContinueWith(t => //这是一个回调
{
  Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
});

例子二:
一个async方法没有返回值的时候,可以返回Task。不同于NoReturn方法,下面这个方法就是返回Task的:

private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task
{
  //这里还是主线程的id
  Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
  Task task = Task.Run(() =>
  {
    Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(1000);
    Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
  });
  await task;
  Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
}

在主线程里面这么用:

Task t= NoReturnTask();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
t.Wait();//主线程等待Task的完成  阻塞的
await t;//await后的代码会由线程池的线程执行  非阻塞

带返回值的Task

带返回值的Task,要使用返回值就一定要等子线程计算完毕。
await/async用同步的方式编写代码,但又是非阻塞的。

private static async Task<long> SumAsync()
{
  Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
  long result = 0;
  await Task.Run(() =>
  {
    for (int k = 0; k < 10; k++)
    {
      Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    Thread.Sleep(1000);
    }
    for (long i = 0; i < 999_999_999; i++)
    {
      result += i;
    }
  });

  Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
  await Task.Run(() =>
  {
    for (int k = 0; k < 10; k++)
    {
      Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
      Thread.Sleep(1000);
    }
    for (long i = 0; i < 999999999; i++)
    {
      result += i;
    }
  });
}

主线程可以这么用,t.Resultt.Wait()是等价的,都是阻塞的。

Task<long> t = SumAsync();
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
long lResult = t.Result;//访问result   主线程等待所有的任务挖成 //如果访问Result,就相当于是同步方法!
t.Wait();//等价于上一行

真的返回Task 不是async,要使用返回值就一定要等子线程计算完毕:

private static Task<int> SumFactory()
{
  Console.WriteLine($"SumFactory 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
  TaskFactory taskFactory = new TaskFactory();
  Task<int> iResult = taskFactory.StartNew<int>(() =>
  {
    Thread.Sleep(3000);
    Console.WriteLine($"SumFactory 123 Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    return 123;
  });
  //Console.WriteLine($"This is {iResult.Result}");
  Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
  return iResult;
}

Await Async原理

public static void Show()
{
  Console.WriteLine($"start1 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
  Async();
  Console.WriteLine($"aaa2 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}
public static async void Async()
{
  Console.WriteLine($"ddd5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
  await Task.Run(() =>
  {
    Thread.Sleep(500);
    Console.WriteLine($"bbb3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
  });
  Console.WriteLine($"ccc4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
}

反编译一下可以看原理:
async方法在编译后会生成一个状态机(实现了IAsyncStateMachine接口);
异步状态机:初始化状态——执行就修改状态1——再执行就修改状态0——执行状态1——出现其他状态就结束了;

  • async方法里面的逻辑其实都放在了MoveNext—主线程new一个状态机 状态-1
  • 主线程调用MoveNext—执行了await之前的东西—启动Task—主线程改状态为0—回去干自己的事
  • 子线程再去MoveNext—状态又回归-1—再执行后续的逻辑—如果需要还可以继续循环

文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
C# Advanced Tutorial 2-2-AOP C# Advanced Tutorial 2-2-AOP
主要内容概要1 AOP面向切面编程2 静态AOP实现3 动态代理AOP实现4 静态织入实现AOP5 Unity 依赖注入容器的AOP6 MVC中的Filter 标记特性,然后该方法执行前后就多了逻辑,Invoker调用中心负责反射调用方法,
下一篇 
C# Advanced Tutorial 1-14-Threads03 C# Advanced Tutorial 1-14-Threads03
主要内容概要1 多异常处理和线程取消2 多线程的临时变量3 线程安全和锁lock4 await async 多线程异常思考:多线程中如果某一个线程异常了,就会终结当前线程;对其他的线程是没有影响的;多线程中的异常去哪儿了? 被吞掉了。 t
  目录