async-await如何阻止?

时间:2013-12-13 18:36:40

标签: c# async-await

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

2 个答案:

答案 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));
            }
        }
    }
}