Хорошо, у меня наконец есть реальный ответ. Я вроде все решил сам, но только после того, как Лучиан Висчик из команды VB подтвердил, что для этого есть все основания. Большое спасибо ему - и, пожалуйста, посетите его блог , который качается.
Значение 0 здесь только специальное, потому что оно не , действительное состояние, в котором вы можете находиться перед await
в обычном случае. В частности, это не состояние, которое конечный автомат может в конечном итоге проверить в другом месте. Я считаю, что использование любого неположительного значения будет работать так же хорошо: -1 не используется для этого, так как логически неверно, так как -1 обычно означает «закончено». Я мог бы утверждать, что в данный момент мы придаем дополнительный смысл состоянию 0, но в конечном итоге это не имеет большого значения. Смысл этого вопроса состоял в том, чтобы выяснить, почему государство вообще устанавливается.
Значение имеет значение, если ожидание заканчивается исключением, которое перехвачено. В конечном итоге мы можем снова вернуться к тому же самому заявлению ожидания, но мы не должны находиться в состоянии, означающем «я вот-вот вернусь из этого ожидания», так как иначе все виды кода были бы пропускаются. Проще всего показать это на примере. Обратите внимание, что я сейчас использую второй CTP, поэтому сгенерированный код немного отличается от кода в вопросе.
Вот асинхронный метод:
static async Task<int> FooAsync()
{
var t = new SimpleAwaitable();
for (int i = 0; i < 3; i++)
{
try
{
Console.WriteLine("In Try");
return await t;
}
catch (Exception)
{
Console.WriteLine("Trying again...");
}
}
return 0;
}
Концептуально, SimpleAwaitable
может быть любым ожидаемым - может быть, задание, может быть, что-то еще. Для моих тестов он всегда возвращает false для IsCompleted
и выдает исключение в GetResult
.
Вот сгенерированный код для MoveNext
:
public void MoveNext()
{
int returnValue;
try
{
int num3 = state;
if (num3 == 1)
{
goto Label_ContinuationPoint;
}
if (state == -1)
{
return;
}
t = new SimpleAwaitable();
i = 0;
Label_ContinuationPoint:
while (i < 3)
{
// Label_ContinuationPoint: should be here
try
{
num3 = state;
if (num3 != 1)
{
Console.WriteLine("In Try");
awaiter = t.GetAwaiter();
if (!awaiter.IsCompleted)
{
state = 1;
awaiter.OnCompleted(MoveNextDelegate);
return;
}
}
else
{
state = 0;
}
int result = awaiter.GetResult();
awaiter = null;
returnValue = result;
goto Label_ReturnStatement;
}
catch (Exception)
{
Console.WriteLine("Trying again...");
}
i++;
}
returnValue = 0;
}
catch (Exception exception)
{
state = -1;
Builder.SetException(exception);
return;
}
Label_ReturnStatement:
state = -1;
Builder.SetResult(returnValue);
}
Мне пришлось переместить Label_ContinuationPoint
, чтобы сделать его допустимым кодом - иначе это не входит в сферу действия goto
- но это не влияет на ответ.
Подумайте о том, что происходит, когда GetResult
выдает свое исключение. Мы пройдем через блок catch, увеличиваем i
, а затем снова зациклимся (предполагая, что i
все еще меньше 3). Мы все еще находимся в том состоянии, в котором мы были до вызова GetResult
... но когда мы попадаем внутрь блока try
, мы должны напечатать "In Try" и снова вызвать GetAwaiter
... и мы сделаем это только в том случае, если состояние не равно 1. Без назначения state = 0
он будет использовать существующего ожидающего и пропустит вызов Console.WriteLine
.
Это довольно извилистый код, через который нужно проработать, но это просто показывает, о чем думает команда. Я рад, что не несу ответственности за это:)