async-await如何阻止?

Nic*_*ckL 13 c# async-await

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

Ste*_*ary 20

不,阻止不会移动到其他地方.返回等待类型的BCL方法使用诸如重叠I/O和I/O完成端口之类的技术来实现完全异步体验.

最近有一篇博客文章描述了它如何一直运行到物理设备并返回.

  • 凉!当我想到这个问题时,我真的在读你的博客.在我再次进行stackoverflow之前,看起来我必须阅读你的所有帖子! (4认同)
  • @NickL,你并不孤单。:) (2认同)

Sco*_*ain 12

Async-await实际上正在为您重写代码.它的作用是使用Task Continuation并将该continuation重新放回创建continuation时当前的Synchronization上下文.

所以功能如下

public async Task Example()
{
    Foo();
    string barResult = await BarAsync();
    Baz(barResult);
}
Run Code Online (Sandbox Code Playgroud)

变成了类似(但不完全是)的东西

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);
                    });
}
Run Code Online (Sandbox Code Playgroud)

现在它实际上要复杂得多,但这是它的基本要点.


真正发生的是它调用函数,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;
}
Run Code Online (Sandbox Code Playgroud)

这仍然不是发生的事情,但重要的是要带走的是if 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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

关于使我的错误答案更加错误的持续冒险,这里是编译器将我的示例函数转换为的实际反编译代码.

[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;
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你通过那里你会看到有没有提及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);
    }
}
Run Code Online (Sandbox Code Playgroud)

感谢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));
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @hvd我第一次没有仔细阅读这个问题,我100%同意你的看法.我不会删除我的答案,因为我认为它包含有用的信息,但斯蒂芬挑衅是这个问题的"正确"答案.我回答了标题,而不是问题:( (2认同)