Вчора я розповідав про нову функцію C # "async", зокрема, заглиблюючись у те, як виглядав створений код, та 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 # або щось подібне. Я просто намагаюся розібратися, чи є тонка причина цього, що я пропустив :)