简体   繁体   English

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

[英]How to implement await without Async CTP

How would you implement something that works similarly to the Async CTP await keyword? 您将如何实现与Async CTP await关键字类似的功能? Is there a simple implementation that works like await in all cases, or does await require different implementations for different scenarios? 是否有一个简单的实现在所有情况下都像await一样,或者await在不同场景下需要不同的实现?

The new await keyword has similar semantics to the existing yield return keyword in that they both cause the compiler to generate the continuation style state machine for you. 新的await关键字与现有的yield return关键字具有相似的语义,因为它们都会使编译器为您生成连续样式状态机。 So it is possible to hack something together using iterators that has some of the same behaviors as the Async CTP. 因此,可以使用与Async CTP具有一些相同行为的迭代器来一起破解某些东西。

Here is what it would look like. 这是它的样子。

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));
    }
}

Since yield return generates an IEnumerable our coroutine must return an IEnumerable . 由于yield return生成一个IEnumerable我们的协同程序必须返回一个IEnumerable All of the magic happens inside the AsyncHelper.Invoke method. 所有的魔法都发生在AsyncHelper.Invoke方法中。 This is what gets our coroutine (masquerading as a hacked iterator) going. 这就是我们的协程(伪装成黑客迭代器)的原因。 It takes special care to make sure the iterator is always executed on the current synchronization context if one exists which is important when trying to simulate how await works on a UI thread. 需要特别注意确保迭代器始终在当前同步上下文中执行(如果存在),这在尝试模拟await在UI线程上的工作方式时非常重要。 It does this by executing the first MoveNext synchronously and then using SynchronizationContext.Send to do the rest from a worker thread which is also used to asynchronously wait on the individual steps. 它通过同步执行第一个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;
    }
}

The whole bit about the TaskCompletionSource was my attempt at replicating the way await can "return" a value. 关于TaskCompletionSource的全部内容是我尝试复制await可以“返回”一个值的方式。 The problem is that the coroutine has to actually return an IEnumerable since it is nothing more than a hacked iterator. 问题是,协程具有实际返回一个IEnumerable ,因为它只不过是砍死迭代多。 So I needed to come up with an alternate mechanism to capture a return value. 所以我需要提出一种替代机制来捕获返回值。

There are some glaring limitations with this, but I hope this gives you the general idea. 这有一些明显的局限性,但我希望这能给你一般的想法。 It also demonstrates how the CLR could have one generalized mechanism for implementing coroutines for which await and yield return would use ubiquitously, but in different ways to provide their respective semantics. 它还演示了CLR如何能够用于实现其协同程序一个通用的机制awaityield return将无处不在使用,但以不同的方式提供各自的语义。

await always involves the same kind of transformation - but it's a pretty painful one. await总是涉及同样的转变 - 但这是一个非常痛苦的转变。 The library side of await isn't too complicated, but the tricky bit is that the compiler builds a state machine for you, allowing the continuation to jump back to the right place. await侧不是太复杂,但棘手的是编译器为您构建状态机,允许继续跳回到正确的位置。

It's possible that my some hacky use of iterator blocks (yield return) you could fake something similar... but it would be pretty ugly. 可能会使用迭代器块(产量返回),你可以伪造类似的东西......但它会非常难看。

I gave a DevExpress webinar on what the compiler is doing behind the scenes a few weeks ago - that shows decompiled code from a couple of examples, as well as explaining how the compiler builds a task to return, and what the "awaiter" has to do. 几周前我给出了一个关于编译器在幕后做什么的DevExpress网络研讨会 - 它显示了几个例子中的反编译代码,以及解释编译器如何构建要返回的任务,以及“awaiter”所具有的内容做。 It may be useful to you. 它可能对您有用。

There are few implementations and examples of coroutines made out of iterators (yield). 用迭代器(yield)做的协程的实现和示例很少。

One of the examples is Caliburn.Micro framework, that uses this patter for asynchronous GUI operations. 其中一个例子是Caliburn.Micro框架,它使用这种模式进行异步GUI操作。 But it can easily be generalised for general async code. 但它可以很容易地推广到一般的异步代码。

The MindTouch DReAM framework implements Coroutines on top of the Iterator pattern which is functionally very similar to Async/Await: MindTouch DReAM框架在Iterator模式之上实现了Coroutines,它在功能上与Async / Await非常相似:

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

vs.

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

Result is DReAM's version of Task . Result是DReAM的Task版本。 The framework dlls work with .NET 2.0+, but to build it you need 3.5, since we're using a lot of 3.5 syntax these days. 框架dll可以与.NET 2.0+一起使用,但要构建它需要3.5,因为我们现在使用了很多3.5语法。

Bill Wagner from Microsoft wrote an article in MSDN Magazine about how you can use the Task Parallel Library in Visual Studio 2010 to implement async like behavior without adding a dependency on the async ctp. 微软的Bill Wagner 在MSDN杂志上了一篇关于如何使用Visual Studio 2010中的任务并行库来实现异步行为而不添加对异步ctp的依赖的文章。

It uses Task and Task<T> extensively which also has the added benefit that once C# 5 is out, your code will be well prepared to start using async and await . 它广泛使用TaskTask<T> ,这也有一个额外的好处,一旦C#5出来,你的代码将准备好开始使用asyncawait

From my reading, the major differences between yield return and await is that await can provide explicitly return a new value into the continuation. 从我的阅读中, yield returnawait之间的主要区别在于await可以明确地向延续中返回一个新值。

SomeValue someValue = await GetMeSomeValue();

whereas with yield return , you'd have to accomplish the same thing by reference. 而对于yield return ,你必须通过引用完成同样的事情。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM