异步编程之async&await
async&await定义
首先async&await是语法糖。是C#5.0后支持的一种异步编程快捷方式。async书写在方法上,表示该方法是一个异步方法,同时,async与await一定是配套使用的,async异步方法的返回类型仅有三种: void,Task,Task<T>方法内部使用await关键字标明开始执行异步代码。 await运算符的操作数通常是以下其中一个 .NET 类型:
Task、Task<TResult>、ValueTask 或 ValueTask<TResult>
但是,任何可等待表达式都可以是 await
运算符的操作数。 有关详细信息,请参阅 C# 语言规范中的可等待表达式部分。
异步方法写法如下:
public async void WithOutReturn() { await Task.Factory.StartNew(()=>{ Console.WriteLine("task is running...."); }); await foreach... //C#8.0开始支持迭代异步流 await using... //C#8.0使用 await using 语句来处理异步可释放对象 }
执行顺序
主线程执行async异步方法时,会同步执行await标志前的代码,当遇到await语句时,主线程会返回到调用async语句处执行后续语句,await标志的方法或者语句为异步执行,其后的代码相当于"回调函数",在await执行完后继续执行。
举例说明:
同步例子:
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { NoReturnNoAwait(); } Console.WriteLine($"Main Thread Task ManagedThreadId= {Thread.CurrentThread.ManagedThreadId}"); Console.Read(); } private static void NoReturnNoAwait() { //主线程执行 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异步方法不带返回值
异步例子:
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { NoReturn(); } 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; //主线程到await这里就返回了,执行主线程任务 //同时task的子线程就开始工作,直到Task完成,然后继续后续任务(后续任务的线程ID不一定是这个子线程,可以是子线程,也可以是其他线程,还可以是主线程) //像什么? 效果上等价于continuewith //task.ContinueWith(t => //{ // Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); //}); Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }
await标志前的代码是同步执行,await标志的方法是异步执行,await标志的方法后面的代码相当于"回调函数",在await标志的异步方法后面执行。
async异步方法带返回值Task
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); }
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}"); //return; //return new TaskFactory().StartNew(() => { }); //不能return 没有async才行 }
当此处加上return语句后,编译器提示:
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 CS1997 由于“AwaitAsyncClass.NoReturnTask()”是返回“Task”的异步方法,因此返回关键字不能后接对象表达式。是否要返回“Task<T>”?
在使用async/await异步方法时,如果方法里面如果没有返回值,默认返回一个Task,其表示该异步方法无返回值。
无返回值的情况下, async Task 等同于async void,两者间的区别是:异步函数签名为async Task(Task<T>)时,函数调用处可以使用await, Task.WhenAny, Task.WhenAll等方式组合使用;而Async Void 则不行。
接收异步方法的task一般可以这样处理:
1、阻塞式
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); t.Wait();//主线程等待Task的完成 阻塞的【调用Task.Wait()
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
}
Console.Read();
}
2、非阻塞
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); await t;//await后的代码会由线程池的线程执行 非阻塞 Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); }
结果与上面的一致。是因为当主线程执行Test()方法中await t后,返回Test()调用处继续往下执行。Test()方法中Task t = NoReturnTask()在await之前,所以会先执行完毕(虽然开启的是一个子线程),此时test里面的
Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
等同于
t.ContinueWith(t =>
{Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");});
再增加几条打印,修改子线程休眠顺序,结果更加清晰。
public static void TestShow() { Test(); Console.WriteLine($"TestShow Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine("主线程执行Test后,遇await返回,往下执行"); }
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); { Task t = NoReturnTask(); await t;//await后的代码会由线程池的线程执行 非阻塞 Console.WriteLine($"Thread Task in Test method ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); } Console.Read(); }
private static async Task NoReturnTask() //在async/await方法里面如果没有返回值,默认返回一个Task { //这里还是主线程的id Console.WriteLine($"NoReturnTask Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Task task = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"NoReturnTask Sleep3000 before,ThreadId={Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"NoReturnTask Sleep3000 after,ThreadId={Thread.CurrentThread.ManagedThreadId}"); }); await task; Console.WriteLine($"NoReturnTask Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}"); //return; // return new TaskFactory().StartNew(() => { }); //不能return 没有async才行 }
async异步方法带返回值Task<T>
private async static Task Test() { Console.WriteLine($"当前主线程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Task<long> t = SumAsync(); Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long lResult = t.Result;//访问result,阻塞式 主线程等待所有的任务挖成 //如果访问Result,就相当于是同步方法! //t.Wait();//等价于上一行,阻塞式--同步 //await t;//非阻塞, }
/// <summary> /// 带返回值的Task /// 要使用返回值就一定要等子线程计算完毕 /// </summary> /// <returns>async 就只返回long</returns> private static async Task<long> SumAsync() { Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); long result = 0; int sum = 5; await Task.Run(() => { for (int k = 0; k < sum; 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 < sum; k++) { Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); } for (long i = 0; i < 999999999; i++) { result += i; } }); Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); await Task.Run(() => { for (int k = 0; k < sum; k++) { Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); } for (long i = 0; i < 999999999; i++) { result += i; } }); Console.WriteLine($"SumFactory 111 end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}"); return result; }
原理解析
static void Main(string[] args) { Test(); Console.WriteLine($"main thread id={Thread.CurrentThread.ManagedThreadId}"); Console.ReadKey(); } static async void Test() { await Task.Factory.StartNew(() => { Task.Delay(1000);//当前任务延时1s后执行 Console.WriteLine($"execute proc,thread id={Thread.CurrentThread.ManagedThreadId}"); }); Console.WriteLine($"after await,thread id={Thread.CurrentThread.ManagedThreadId} "); }
通过ILSpy查看其C#代码如下:
注意:ILSpy 选项->反编译器项要去掉C#5.0(反编译异步方法条目)
private static void Main(string[] args) { Test(); Console.WriteLine($"main thread id={Thread.CurrentThread.ManagedThreadId}"); Console.ReadKey(); } [AsyncStateMachine(typeof(<Test>d__1))] [DebuggerStepThrough] private static void Test() { <Test>d__1 stateMachine = new <Test>d__1(); stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create(); stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); }
Main方法没有变化,Test方法与原来的不同,看来编译器帮我们做了许多事情来通过async,await来实现异步方法。
这里Test方法主要做了这几件事情:
1.实例化 <Test>d__1类对象 stateMachine(状态机),并对 t__builder和1__state(状态)赋初值
2.执行 stateMachine.<>t__builder.Start(ref stateMachine);【实际是调用 AsyncVoidMethodBuilder结构体下的 Start方法】
我们首先看 <Test>d__1类
[CompilerGenerated] private sealed class <Test>d__1 : IAsyncStateMachine { public int <>1__state; public AsyncVoidMethodBuilder <>t__builder;//此处Test同步方法时一个void函数,其对应的method builder为AsyncVoidMethodBuilder
private TaskAwaiter <>u__1; private void MoveNext() { int num = <>1__state; try { TaskAwaiter awaiter; if (num != 0) { awaiter = Task.Factory.StartNew(delegate { Task.Delay(1000); Console.WriteLine($"execute proc,thread id={Thread.CurrentThread.ManagedThreadId}"); }).GetAwaiter(); if (!awaiter.IsCompleted) { num = (<>1__state = 0); <>u__1 = awaiter; <Test>d__1 stateMachine = this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); return; } } else { awaiter = <>u__1; <>u__1 = default(TaskAwaiter); num = (<>1__state = -1); } awaiter.GetResult(); Console.WriteLine($"after await,thread id={Thread.CurrentThread.ManagedThreadId} "); } catch (Exception exception) { <>1__state = -2; <>t__builder.SetException(exception); return; } <>1__state = -2; <>t__builder.SetResult();
AsyncVoidMethodBuilder结构体下的 Start方法
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{ if (stateMachine == null) { throw new ArgumentNullException("stateMachine"); } ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); RuntimeHelpers.PrepareConstrainedRegions(); try { ExecutionContext.EstablishCopyOnWriteScope(ref ecsw); stateMachine.MoveNext();// } finally { ecsw.Undo(); } }
此处泛型约束stateMachine变量是实现IAsyncStateMachine接口的对象,而实际传入的ref stateMachine为实现了
IAsyncStateMachine接口的<Test>d__1密封类,故stateMachine.MoveNext()实际调用的是<Test>d__1下的MoveNext()方法:
说明:此处如果awaiter
// System.Runtime.CompilerServices.AsyncVoidMethodBuilder using System.Security; using System.Threading.Tasks; [SecuritySafeCritical] [__DynamicallyInvokable] public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { try { AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null; Action completionAction = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? Task : null, ref runnerToInitialize);//创建action回调函数 if (m_coreState.m_stateMachine == null) { if (AsyncCausalityTracer.LoggingOn) { AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, Task.Id, "Async: " + stateMachine.GetType().Name, 0uL); } m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, null); } awaiter.UnsafeOnCompleted(completionAction);//Action交给Awaiter,让它在await的操作完成后执行这个Action,后续可以看到这个action委托绑定的函数实际为run,即awaiter操作完毕->action动作执行->run方法开始触发执行 } catch (Exception exception) { AsyncMethodBuilderCore.ThrowAsync(exception, null); } }
此处主要完成两件事:
一是创建了一个Action,MoveNext方法的信息已经随着stateMachine被封装进去了。
二是把上面这个Action交给Awaiter,让它在awaiter的操作完成后执行这个Action。
而moveNextRunner.Run方法实际所做的事情就是执行此时状态机的MoveNext方法。
即执行stateMachine.MoveNext()
促使状态机继续进行状态流转,迭代。
【ExecutionContext上下文后续研究】
[SecuritySafeCritical] internal void Run() { if (m_context != null) { try { ContextCallback callback = InvokeMoveNext; ExecutionContext.Run(m_context, callback, m_stateMachine, preserveSyncCtx: true); } finally { m_context.Dispose(); } } else { m_stateMachine.MoveNext(); } }
[SecurityCritical] private static void InvokeMoveNext(object stateMachine) { ((IAsyncStateMachine)stateMachine).MoveNext(); }