6

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

4

6 回答 6

9

newawait关键字与现有关键字具有相似的语义,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方法内部。这就是我们的协程(伪装成被黑的迭代器)运行的原因。如果存在迭代器,则需要特别注意确保始终在当前同步上下文上执行迭代器,这在尝试模拟awaitUI 线程上的工作方式时很重要。它通过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普遍使用这些协同程序,但以不同的方式提供它们各自的语义。

于 2011-07-15T19:48:52.040 回答
8

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

可能我对迭代器块的一些hacky使用(收益回报)你可以伪造类似的东西......但这会很丑陋。

几周前,我举办了一次DevExpress 网络研讨会,讨论编译器在幕后所做的事情——展示了几个示例中的反编译代码,并解释了编译器如何构建要返回的任务,以及“等待者”必须做什么做。它可能对你有用。

于 2011-03-23T06:27:50.740 回答
2

很少有由迭代器(yield)组成的协同程序的实现和示例。

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

于 2011-03-23T06:32:52.603 回答
2

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

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

对比

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

Result是 DReAM 的版本Task。框架 dll 可与 .NET 2.0+ 一起使用,但要构建它,您需要 3.5,因为这些天我们使用了大量的 3.5 语法。

于 2011-05-28T19:54:31.917 回答
1

来自 Microsoft 的 Bill Wagner在 MSDN 杂志上写了一篇文章,介绍了如何使用 Visual Studio 2010 中的任务并行库来实现类似异步的行为,而无需添加对异步 ctp 的依赖。

它使用TaskTask<T>广泛使用还有一个额外的好处,即一旦 C# 5 出来,您的代码将准备好开始使用asyncawait.

于 2011-11-17T08:38:47.030 回答
0

yield return从我的阅读来看,和之间的主要区别在于await可以await显式地将新值返回到延续中。

SomeValue someValue = await GetMeSomeValue();

而使用yield return,您必须通过引用来完成相同的事情。

var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;
于 2011-10-04T11:38:01.493 回答