【发布时间】:2013-12-13 18:36:40
【问题描述】:
我认为异步方法有利于 IO 工作,因为它们不会在等待线程时阻塞线程,但这实际上是怎么可能的呢?我认为必须有一些东西在监听才能触发任务完成,所以这是否意味着阻塞只是移动到了其他地方?
【问题讨论】:
标签: c# async-await
我认为异步方法有利于 IO 工作,因为它们不会在等待线程时阻塞线程,但这实际上是怎么可能的呢?我认为必须有一些东西在监听才能触发任务完成,所以这是否意味着阻塞只是移动到了其他地方?
【问题讨论】:
标签: c# async-await
不,阻塞不会移动到其他任何地方。返回可等待类型的 BCL 方法使用重叠 I/O 与 I/O 完成端口等技术,以获得完全异步的体验。
我有一个recent blog post,它描述了它是如何一直到物理设备并返回的。
【讨论】:
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 为真,它将同步运行回发代码,而不是立即返回。
很酷的是,你不需要等待一个Task,你可以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));
}
}
}
}
【讨论】:
BarAsync 没有要求在内部使用阻塞,它可能只是你放在后台线程上需要很长时间来处理的东西。阻塞涉及停止代码并等待外部源,您可以这样做(例如 Stepen 谈到的 IO 完成端口),但这不是必需的。