我认为异步方法对IO工作有好处,因为它们在等待时不会阻塞线程,但这实际上是如何实现的呢?我假设必须要听一些东西来触发任务才能完成,这是否意味着阻塞只是移动到其他地方?
答案 0 :(得分:20)
不,阻止不会移动到其他地方。返回等待类型的BCL方法使用诸如重叠I / O和I / O完成端口之类的技术来实现完全异步体验。
我有一个recent blog post,它描述了它如何一直运行到物理设备并返回。
答案 1 :(得分:12)
Async-await实际上正在为您重写代码。它的作用是使用Task Continuation并将该continuation重新放回到创建continuation时当前的Synchronization上下文。
以下功能
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
为真,它将同步运行回发代码而不是立即返回。
很酷的是,你不需要等待任务,你可以await anything,只要它有一个名为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() { ... }
}
关于making my wrong answer even more wrong上的持续冒险,这是编译器将我的示例函数转换为的实际反编译代码。
[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
时,编译器实际上已转基于state machine界面,您的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);
}
}
感谢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));
}
}
}
}