Подтвердить что ты не робот

Как асинхронный ожидания не блокируется?

Я понимаю, что методы async хороши для работы с IO, потому что они не блокируют поток, пока их ждут, но как это возможно? Я предполагаю, что что-то должно случиться, чтобы запустить задачу, так значит ли это, что блокировка просто перемещена в другое место?

4b9b3361

Ответ 1

Нет, блокировка не перемещается нигде. Методы BCL, возвращающие ожидаемые типы, используют такие методы, как перекрывающиеся ввода-вывода с портами ввода-вывода для полностью асинхронного использования.

У меня есть недавний пост в блоге, в котором описывается, как это работает вплоть до физического устройства и обратно.

Ответ 2

Async-wait фактически переписывает ваш код для вас. То, что он делает, это использование продолжения задачи и добавляет это продолжение в контекст синхронизации, который был текущим, когда было создано продолжение.

Итак, следующая функция

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 истинно, она будет запускать код обратной передачи синхронно, а не сразу возвращаться.

Самое интересное, что вам не нужно ждать в Задаче, вы можете ждать чего-нибудь, если у него есть функция называется 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() { ... }
}

В продолжающемся приключении неправильный ответ не соответствует действительности, вот фактический декомпилированный код, в котором компилятор превращает мою функцию example в.

[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));
            }
        }
    }
}