Я понимаю, что методы async хороши для работы с IO, потому что они не блокируют поток, пока их ждут, но как это возможно? Я предполагаю, что что-то должно случиться, чтобы запустить задачу, так значит ли это, что блокировка просто перемещена в другое место?
Как асинхронный ожидания не блокируется?
Ответ 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));
}
}
}
}