![](/img/trans.png)
[英]Using async-await for database queries — how does that save threads?
[英]How does async-await not block?
我认为异步方法对于IO工作很有用,因为它们在等待时不会阻塞线程,但是实际上这怎么可能呢? 我认为必须监听某些内容才能触发任务完成,这是否意味着该阻塞只是移到其他地方了?
不,障碍物不会移动到其他任何地方。 返回等待类型的BCL方法使用诸如具有I / O完成端口的重叠I / O之类的技术来获得完全异步的体验。
我最近有一篇博客文章 ,描述了它如何一直运行到物理设备,再到物理设备。
异步等待实际上是为您重写代码。 它所做的是使用“任务连续性”,并将该连续性放回到创建连续性时最新的“同步”上下文中。
所以下面的功能
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
为true,它将同步运行回发代码,而不是立即返回。
很酷的事情是,您不需要等待Task,只要它具有一个名为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);
}
}
感谢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));
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.