Новое ключевое слово await
имеет семантику, аналогичную существующему ключевому слову yield return
, поскольку оба они заставляют компилятор генерировать для вас конечный автомат стиля продолжения. Таким образом, можно взломать что-то вместе, используя итераторы, которые имеют такое же поведение, что и Async CTP.
Вот как это будет выглядеть.
public class Form1 : Form
{
private void Button1_Click(object sender, EventArgs e)
{
AsyncHelper.Invoke<bool>(PerformOperation);
}
private IEnumerable<Task> PerformOperation(TaskCompletionSource<bool> tcs)
{
Button1.Enabled = false;
for (int i = 0; i < 10; i++)
{
textBox1.Text = "Before await " + Thread.CurrentThread.ManagedThreadId.ToString();
yield return SomeOperationAsync(); // Await
textBox1.Text = "After await " + Thread.CurrentThread.ManagedThreadId.ToString();
}
Button2.Enabled = true;
tcs.SetResult(true); // Return true
}
private Task SomeOperationAsync()
{
// Simulate an asynchronous operation.
return Task.Factory.StartNew(() => Thread.Sleep(1000));
}
}
Поскольку yield return
генерирует IEnumerable
, наша сопрограмма должна вернуть IEnumerable
. Вся магия происходит внутри метода AsyncHelper.Invoke
. Это то, что запускает нашу сопрограмму (маскирующуюся под взломанный итератор). Особое внимание уделяется тому, чтобы итератор всегда выполнялся в текущем контексте синхронизации, если он существует, что важно при попытке смоделировать, как await
работает в потоке пользовательского интерфейса. Это выполняется путем синхронного выполнения первого MoveNext
и последующего использования SynchronizationContext.Send
для выполнения остального из рабочего потока, который также используется для асинхронного ожидания на отдельных шагах.
public static class AsyncHelper
{
public static Task<T> Invoke<T>(Func<TaskCompletionSource<T>, IEnumerable<Task>> method)
{
var context = SynchronizationContext.Current;
var tcs = new TaskCompletionSource<T>();
var steps = method(tcs);
var enumerator = steps.GetEnumerator();
bool more = enumerator.MoveNext();
Task.Factory.StartNew(
() =>
{
while (more)
{
enumerator.Current.Wait();
if (context != null)
{
context.Send(
state =>
{
more = enumerator.MoveNext();
}
, null);
}
else
{
enumerator.MoveNext();
}
}
}).ContinueWith(
(task) =>
{
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(default(T));
}
});
return tcs.Task;
}
}
Все, что касалось TaskCompletionSource
, было моей попыткой воспроизвести способ, которым await
может "вернуть" значение. Проблема в том, что сопрограмма имеет , чтобы фактически вернуть IEnumerable
, поскольку это не более чем взломанный итератор. Поэтому мне нужно было придумать альтернативный механизм для захвата возвращаемого значения.
Есть некоторые явные ограничения с этим, но я надеюсь, что это дает вам общее представление. Это также демонстрирует, как CLR может иметь один обобщенный механизм для реализации сопрограмм, для которого await
и yield return
будут использоваться повсеместно, но по-разному для обеспечения их соответствующей семантики.