Вчера я говорил о новой функции асинхронности в C #, в частности, о том, как выглядит сгенерированный код, и the GetAwaiter()
/ BeginAwait()
/ EndAwait()
вызовах.
Мы подробно рассмотрели конечный автомат, сгенерированный компилятором C #, и мы не могли понять два аспекта:
- Почему сгенерированный класс содержит
Dispose()
метод и$__disposing
переменную, которые никогда не используются (а класс не реализуетсяIDisposable
). - Почему внутренняя
state
переменная установлена в 0 перед любым вызовомEndAwait()
, когда 0 обычно означает «это начальная точка входа».
Я подозреваю, что на первый вопрос можно ответить, сделав что-то более интересное в асинхронном методе, хотя, если у кого-то будет какая-либо дополнительная информация, я буду рад ее услышать. Этот вопрос больше касается второго пункта, однако.
Вот очень простой пример кода:
using System.Threading.Tasks;
class Test
{
static async Task<int> Sum(Task<int> t1, Task<int> t2)
{
return await t1 + await t2;
}
}
... и вот код, который генерируется для MoveNext()
метода, который реализует конечный автомат. Это скопировано непосредственно из Reflector - я не исправил невыразимые имена переменных:
public void MoveNext()
{
try
{
this.$__doFinallyBodies = true;
switch (this.<>1__state)
{
case 1:
break;
case 2:
goto Label_00DA;
case -1:
return;
default:
this.<a1>t__$await2 = this.t1.GetAwaiter<int>();
this.<>1__state = 1;
this.$__doFinallyBodies = false;
if (this.<a1>t__$await2.BeginAwait(this.MoveNextDelegate))
{
return;
}
this.$__doFinallyBodies = true;
break;
}
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
this.<a2>t__$await4 = this.t2.GetAwaiter<int>();
this.<>1__state = 2;
this.$__doFinallyBodies = false;
if (this.<a2>t__$await4.BeginAwait(this.MoveNextDelegate))
{
return;
}
this.$__doFinallyBodies = true;
Label_00DA:
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
this.<>1__state = -1;
this.$builder.SetResult(this.<1>t__$await1 + this.<2>t__$await3);
}
catch (Exception exception)
{
this.<>1__state = -1;
this.$builder.SetException(exception);
}
}
Это долго, но важные строки для этого вопроса таковы:
// End of awaiting t1
this.<>1__state = 0;
this.<1>t__$await1 = this.<a1>t__$await2.EndAwait();
// End of awaiting t2
this.<>1__state = 0;
this.<2>t__$await3 = this.<a2>t__$await4.EndAwait();
В обоих случаях состояние снова изменяется, прежде чем оно, очевидно, будет наблюдаться ... так зачем вообще устанавливать его в 0? Если бы MoveNext()
в этот момент вызывали снова (напрямую или через Dispose
), он снова эффективно запускал бы асинхронный метод, что, насколько я могу сказать, было бы совершенно неуместным ... если и MoveNext()
не вызывается , изменение состояния не имеет значения.
Является ли это просто побочным эффектом повторного использования компилятором кода генерации блока итератора для асинхронного преобразования, где это может иметь более очевидное объяснение?
Важный отказ от ответственности
Очевидно, это всего лишь компилятор CTP. Я полностью ожидаю, что все изменится до окончательного выпуска - и, возможно, даже до следующего выпуска CTP. Этот вопрос никоим образом не пытается утверждать, что это недостаток компилятора C # или чего-то в этом роде. Я просто пытаюсь понять, есть ли тонкая причина для этого, которую я пропустил :)