我了解async方法对于IO操作很有用,因为它们在等待期间不会阻塞线程,但这究竟是如何实现的呢?我猜测必须有某个监听器来触发任务完成,那么这是否意味着阻塞只是被移动到其他地方了呢?
我了解async方法对于IO操作很有用,因为它们在等待期间不会阻塞线程,但这究竟是如何实现的呢?我猜测必须有某个监听器来触发任务完成,那么这是否意味着阻塞只是被移动到其他地方了呢?
不,阻塞操作没有移动到其他地方。返回可等待类型的BCL方法使用诸如重叠I/O和I/O完成端口等技术,以获得完全异步的体验。
我有一篇最近的博客文章,描述了这是如何一直运作到物理设备并返回的。
Async-await实际上是为您重写代码。它使用任务继续,并将该继续放回到创建继续时的同步上下文中。
因此,以下函数
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<TResult> : 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.<Example>d__0))]
public Task Example()
{
Form1.<Example>d__0 <Example>d__;
<Example>d__.<>4__this = this;
<Example>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
<Example>d__.<>1__state = -1;
AsyncTaskMethodBuilder <>t__builder = <Example>d__.<>t__builder;
<>t__builder.Start<Form1.<Example>d__0>(ref <Example>d__);
return <Example>d__.<>t__builder.Task;
}
现在,如果你仔细看一下,你会发现没有提到Foo()
,BarAsync()
或者Baz(barResult)
,这是因为当你使用async
时,编译器实际上将你的函数转换成了基于IAsyncStateMachine
接口的状态机。如果我们去看一下,编译器生成了一个名为<Example>d__0
的新结构。
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <Example>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public Form1 <>4__this;
public string <barResult>5__1;
private TaskAwaiter<string> <>u__$awaiter2;
private object <>t__stack;
void IAsyncStateMachine.MoveNext()
{
try
{
int num = this.<>1__state;
if (num != -3)
{
TaskAwaiter<string> 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<TaskAwaiter<string>, Form1.<Example>d__0>(ref taskAwaiter, ref this);
return;
}
}
else
{
taskAwaiter = this.<>u__$awaiter2;
this.<>u__$awaiter2 = default(TaskAwaiter<string>);
this.<>1__state = -1;
}
string arg_92_0 = taskAwaiter.GetResult();
taskAwaiter = default(TaskAwaiter<string>);
string text = arg_92_0;
this.<barResult>5__1 = text;
this.<>4__this.Baz(this.<barResult>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);
}
}
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));
}
}
}
}
BarAsync
不要求在内部使用阻塞,它可以只是需要长时间处理的任务,你可以将其放在后台线程上。阻塞涉及停止代码并等待外部资源,你可以这样做(例如 Stepen 谈到的 IO 完成端口),但这不是必需的。 - Scott Chamberlain