Вчера я рассказывал о новой функции "async" на С#, в частности вникая в то, что выглядел сгенерированный код, и вызовы the GetAwaiter()
/BeginAwait()
/EndAwait()
.
Мы подробно рассмотрели на машине состояний, сгенерированной компилятором С#, и были два аспекта, которые мы не могли понять:
- Почему сгенерированный класс содержит метод
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
), это эффективно запустило бы асинхронный метод снова, что было бы совершенно неуместным, насколько я могу сказать... if и MoveNext()
не является так как изменение состояния не имеет значения.
Является ли это просто побочным эффектом компилятора, повторно использующего код генерации блока итератора для async, где он может иметь более очевидное объяснение?
Важная оговорка
Очевидно, что это просто компилятор CTP. Я полностью ожидаю, что ситуация изменится до финальной версии - и, возможно, даже до следующей версии CTP. Этот вопрос никоим образом не пытается утверждать, что это недостаток в компиляторе С# или что-то в этом роде. Я просто пытаюсь выяснить, есть ли у меня тонкая причина, которую я пропустил:)