Как реализовать await без Async CTP - PullRequest
6 голосов
/ 23 марта 2011

Как бы вы реализовали что-то, что работает аналогично ключевому слову Async CTP await?Существует ли простая реализация, которая работает как await во всех случаях, или await требует разных реализаций для разных сценариев?

Ответы [ 6 ]

9 голосов
/ 15 июля 2011

Новое ключевое слово 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 будут использоваться повсеместно, но по-разному для обеспечения их соответствующей семантики.

8 голосов
/ 23 марта 2011

await всегда включает в себя один и тот же вид преобразования - но это довольно болезненный. Сторона библиотеки await не слишком сложна, но хитрый момент заключается в том, что компилятор создает для вас конечный автомат, позволяя продолжению вернуться в нужное место.

Это возможно , что при некотором хакерском использовании блоков итераторов (возвращение доходности) вы могли бы подделать нечто подобное ... но это было бы довольно уродливо.

Я провел веб-семинар DevExpress о том, что компилятор делает за кулисами несколько недель назад - который показывает декомпилированный код из пары примеров, а также объясняет, как компилятор создает задачу для возврата и что должен делать «ожидающий». Это может быть полезно для вас.

2 голосов
/ 28 мая 2011

Фреймворк MindTouch DReAM реализует сопрограммы поверх шаблона итератора, который функционально очень похож на Async / Await:

async Task Foo() {
  await SomeAsyncCall();
}

против

IYield Result Foo() {
  yield return SomeAsyncCall();
}

Result является версией DREAM Task.Фреймворк работает с .NET 2.0+, но для его построения вам нужен 3.5, поскольку в наши дни мы используем много синтаксиса 3.5.

2 голосов
/ 23 марта 2011

Существует несколько реализаций и примеров сопрограмм, составленных из итераторов (yield).

Одним из примеров является платформа Caliburn.Micro, которая использует этот шаблон для асинхронных операций с графическим интерфейсом. Но это может быть легко обобщено для общего асинхронного кода.

1 голос
/ 17 ноября 2011

Билл Вагнер из Microsoft написал статью в журнале MSDN о том, как можно использовать библиотеку параллелей задач в Visual Studio 2010 для реализации асинхронного поведения без добавления зависимости от асинхронного ctp.

Он широко использует Task и Task<T>, что также дает дополнительное преимущество: после выхода C # 5 ваш код будет хорошо подготовлен к использованию async и await.

0 голосов
/ 04 октября 2011

Насколько я понимаю, основные различия между yield return и await заключаются в том, что await может обеспечить явное возвращение нового значения в продолжение.нужно сделать то же самое по ссылке.

var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;
...