如何在没有Async CTP的情况下实现等待

时间:2011-03-23 06:24:44

标签: c# .net asynchronous async-await async-ctp

您如何实现与Async CTP await关键字类似的功能?是否有一个简单的实现在所有情况下都像await一样,或者await是否需要针对不同场景的不同实现?

6 个答案:

答案 0 :(得分:9)

新的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在UI线程上的工作方式时非常重要。它通过同步执行第一个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 如何有一个通用机制来实现协同程序,awaityield return将无处不在地使用它们,但是以不同的方式提供它们各自的语义。

答案 1 :(得分:8)

await总是涉及同样的转变 - 但这是一个非常痛苦的转变。 await方面并不复杂,但棘手的是编译器为您构建状态机,允许继续跳回到正确的位置。

可能我的一些hacky使用迭代器块(产量返回)你可以伪造类似的东西......但它会非常难看。

我给了一个DevExpress webinar on what the compiler is doing behind the scenes a few weeks ago - 它显示了几个例子的反编译代码,以及解释编译器如何构建要返回的任务,以及“awaiter”必须做什么。它可能对你有用。

答案 2 :(得分:2)

用迭代器(yield)做的协程的实现和示例很少。

其中一个示例是Caliburn.Micro框架,它使用此模式进行异步GUI操作。但它可以很容易地推广为一般的异步代码。

答案 3 :(得分:2)

MindTouch DReAM框架在Iterator模式之上实现了Coroutines,它在功能上与Async / Await非常相似:

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

VS

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

Result是DReAM的Task版本。框架dll适用于.NET 2.0+,但要构建它需要3.5,因为我们现在使用了很多3.5语法。

答案 4 :(得分:1)

来自Microsoft的Bill Wagner写了an article in MSDN Magazine关于如何使用Visual Studio 2010中的任务并行库来实现异步行为,而不依赖于异步ctp。

它广泛使用TaskTask<T>,这也带来了额外的好处,一旦C#5退出,您的代码就会做好充分准备,开始使用asyncawait

答案 5 :(得分:0)

根据我的阅读,yield returnawait之间的主要区别在于await可以明确地向续集中返回一个新值。

SomeValue someValue = await GetMeSomeValue();

而对于yield return,你必须通过引用来完成同样的事情。

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