Ответ 1
Это потому, что ключевые слова async
и await
- это просто синтаксический сахар для чего-то, называемого сопрограммы.
Нет никаких специальных инструкций IL для поддержки создания асинхронных методов. Вместо этого метод async можно рассматривать как своего рода конечный автомат каким-то образом.
Я попытаюсь сделать этот пример как можно короче:
[TestClass]
public class AsyncTest
{
[TestMethod]
public async Task RunTest_1()
{
var result = await GetStringAsync();
Console.WriteLine(result);
}
private async Task AppendLineAsync(StringBuilder builder, string text)
{
await Task.Delay(1000);
builder.AppendLine(text);
}
public async Task<string> GetStringAsync()
{
// Code before first await
var builder = new StringBuilder();
var secondLine = "Second Line";
// First await
await AppendLineAsync(builder, "First Line");
// Inner synchronous code
builder.AppendLine(secondLine);
// Second await
await AppendLineAsync(builder, "Third Line");
// Return
return builder.ToString();
}
}
Это какой-то асинхронный код, так как вы, вероятно, привыкли: наш метод GetStringAsync
сначала создает StringBuilder
синхронно, затем он ожидает некоторые асинхронные методы и, наконец, возвращает результат. Как это реализовать, если не было ключевого слова await
?
Добавьте следующий код в класс AsyncTest
:
[TestMethod]
public async Task RunTest_2()
{
var result = await GetStringAsyncWithoutAwait();
Console.WriteLine(result);
}
public Task<string> GetStringAsyncWithoutAwait()
{
// Code before first await
var builder = new StringBuilder();
var secondLine = "Second Line";
return new StateMachine(this, builder, secondLine).CreateTask();
}
private class StateMachine
{
private readonly AsyncTest instance;
private readonly StringBuilder builder;
private readonly string secondLine;
private readonly TaskCompletionSource<string> completionSource;
private int state = 0;
public StateMachine(AsyncTest instance, StringBuilder builder, string secondLine)
{
this.instance = instance;
this.builder = builder;
this.secondLine = secondLine;
this.completionSource = new TaskCompletionSource<string>();
}
public Task<string> CreateTask()
{
DoWork();
return this.completionSource.Task;
}
private void DoWork()
{
switch (this.state)
{
case 0:
goto state_0;
case 1:
goto state_1;
case 2:
goto state_2;
}
state_0:
this.state = 1;
// First await
var firstAwaiter = this.instance.AppendLineAsync(builder, "First Line")
.GetAwaiter();
firstAwaiter.OnCompleted(DoWork);
return;
state_1:
this.state = 2;
// Inner synchronous code
this.builder.AppendLine(this.secondLine);
// Second await
var secondAwaiter = this.instance.AppendLineAsync(builder, "Third Line")
.GetAwaiter();
secondAwaiter.OnCompleted(DoWork);
return;
state_2:
// Return
var result = this.builder.ToString();
this.completionSource.SetResult(result);
}
}
Таким образом, очевидно, что код до первого ключевого слова await
остается неизменным. Все остальное преобразуется в конечный автомат, который использует операторы goto
для выполнения вашего предыдущего кода кусочно. Каждый раз, когда одна из ожидаемых задач завершается, конечный автомат переходит к следующему шагу.
Этот пример упрощен, чтобы уточнить, что происходит за кулисами. Добавьте обработку ошибок и некоторые foreach
-Loops в вашем асинхронном методе, а конечный автомат становится намного сложнее.
Кстати, есть еще одна конструкция в С#, которая делает такую вещь: ключевое слово yield
. Это также генерирует машину состояний, и код выглядит очень похоже на то, что создает await
.
Для дальнейшего чтения просмотрите этот CodeProject, который более глубоко изучает сгенерированный конечный автомат.