async-await如何阻止?

我认为异步方法对IO工作有好处,因为它们在等待时不会阻塞线程,但这实际上是如何实现的呢? 我假设必须要听某些事情来触发任务才能完成,这是否意味着阻塞只是移动到其他地方?

不,阻止不会移动到其他地方。 返回等待类型的BCL方法使用诸如重叠I / O和I / O完成端口之类的技术来实现完全异步体验。

我最近有一篇博客文章描述了它如何一直运行到物理设备并返回。

Async-await实际上正在为您重写代码。 它的作用是使用Task Continuation并将该continuation重新放回创建continuation时当前的Synchronization上下文。

所以function如下

public async Task Example() { Foo(); string barResult = await BarAsync(); Baz(barResult); } 

转入类似(但不完全)的东西

 public Task Example() { Foo(); var syncContext = SyncronizationContext.Current; return BarAsync().ContinueWith((continuation) => { Action postback = () => { string barResult = continuation.Result(); Baz(barResult) } if(syncContext != null) syncContext.Post(postback, null); else Task.Run(postback); }); } 

现在它实际上要复杂得多,但这是它的基本要点。


真正发生的是它调用函数GetAwaiter()如果它存在并做更像这样的事情

 public Task Example() { Foo(); var task = BarAsync(); var awaiter = task.GetAwaiter(); Action postback = () => { string barResult = awaiter.GetResult(); Baz(barResult) } if(awaiter.IsCompleted) postback(); else { var castAwaiter = awaiter as ICriticalNotifyCompletion; if(castAwaiter != null) { castAwaiter.UnsafeOnCompleted(postback); } else { var context = SynchronizationContext.Current; if (context == null) context = new SynchronizationContext(); var contextCopy = context.CreateCopy(); awaiter.OnCompleted(() => contextCopy.Post(postback, null)); } } return task; } 

这仍然不是发生的事情,但重要的是要拿走awaiter.IsCompleted是真的,它将同步运行回发代码,而不是立即返回。

很酷的是,你不需要等待任务,你可以等待任何东西 ,只要它有一个名为GetAwaiter()的函数,并且返回的对象可以满足以下签名

 public class MyAwaiter : INotifyCompletion { public bool IsCompleted { get { ... } } public void OnCompleted(Action continuation) { ... } public TResult GetResult() { ... } } //or public class MyAwaiter : INotifyCompletion { public bool IsCompleted { get { ... } } public void OnCompleted(Action continuation) { ... } public void GetResult() { ... } } 

关于使我的错误答案更加错误的持续冒险,这里是编译器将我的示例函数转换为的实际反编译代码。

 [DebuggerStepThrough, AsyncStateMachine(typeof(Form1.d__0))] public Task Example() { Form1.d__0 d__; d__.<>4__this = this; d__.<>t__builder = AsyncTaskMethodBuilder.Create(); d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = d__.<>t__builder; <>t__builder.Startd__0>(ref d__); return d__.<>t__builder.Task; } 

现在,如果你仔细观察,你会看到没有引用Foo()BarAsync()Baz(barResult)这是因为当你使用async ,编译器实际上会根据IAsyncStateMachine接口将你的函数转换为状态机 。 如果我们看看,编译器生成了一个名为d__0的新结构

 [CompilerGenerated] [StructLayout(LayoutKind.Auto)] private struct d__0 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; public Form1 <>4__this; public string 5__1; private TaskAwaiter <>u__$awaiter2; private object <>t__stack; void IAsyncStateMachine.MoveNext() { try { int num = this.<>1__state; if (num != -3) { TaskAwaiter taskAwaiter; if (num != 0) { this.<>4__this.Foo(); taskAwaiter = this.<>4__this.BarAsync().GetAwaiter(); if (!taskAwaiter.IsCompleted) { this.<>1__state = 0; this.<>u__$awaiter2 = taskAwaiter; this.<>t__builder.AwaitUnsafeOnCompleted, Form1.d__0>(ref taskAwaiter, ref this); return; } } else { taskAwaiter = this.<>u__$awaiter2; this.<>u__$awaiter2 = default(TaskAwaiter); this.<>1__state = -1; } string arg_92_0 = taskAwaiter.GetResult(); taskAwaiter = default(TaskAwaiter); string text = arg_92_0; this.5__1 = text; this.<>4__this.Baz(this.5__1); } } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0) { this.<>t__builder.SetStateMachine(param0); } } 

感谢ILSpy的工作人员让他们的工具使用了一个库,您可以自己扩展和调用代码。 要获得上述代码,我所要做的就是

 using System.IO; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Ast; using Mono.Cecil; namespace Sandbox_Console { internal class Program { public static void Main() { AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(@"C:\Code\Sandbox Form\SandboxForm\bin\Debug\SandboxForm.exe"); var context = new DecompilerContext(assembly.MainModule); context.Settings.AsyncAwait = false; //If you don't do this it will show the original code with the "await" keyword and hide the state machine. AstBuilder decompiler = new AstBuilder(context); decompiler.AddAssembly(assembly); using (var output = new StreamWriter("Output.cs")) { decompiler.GenerateCode(new PlainTextOutput(output)); } } } }