如何在没有异步CTP的情况下实现await

发布于 2024-10-26 01:30:00 字数 136 浏览 3 评论 0原文

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

How would you implement something that works similarly to the Async CTP await keyword? Is there a simple implementation that works like await in all cases, or does await require different implementations for different scenarios?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

拥抱我好吗 2024-11-02 01:30:19

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

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

There are few implementations and examples of coroutines made out of iterators (yield).

One of the examples is Caliburn.Micro framework, that uses this patter for asynchronous GUI operations. But it can easily be generalised for general async code.

南城追梦 2024-11-02 01:30:19

MindTouch DReAM 框架在迭代器模式之上实现了协程,其功能与 Async/Await 非常相似

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

: .

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

Result 是 DReAM 的 Task 版本。框架 dll 可与 .NET 2.0+ 配合使用,但要构建它,您需要 3.5,因为现在我们使用大量 3.5 语法。

The MindTouch DReAM framework implements Coroutines on top of the Iterator pattern which is functionally very similar to Async/Await:

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

vs.

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

Result is DReAM's version of 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.

旧梦荧光笔 2024-11-02 01:30:19

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

它广泛使用 TaskTask,这还有一个额外的好处,即一旦 C# 5 发布,您的代码将做好准备开始使用 async< /code> 和 await

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.

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.

愁以何悠 2024-11-02 01:30:19

根据我的阅读,yield returnawait 之间的主要区别在于 await 可以显式地将新值返回到延续中。

SomeValue someValue = await GetMeSomeValue();

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

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

From my reading, the major differences between yield return and await is that await can provide explicitly return a new value into the continuation.

SomeValue someValue = await GetMeSomeValue();

whereas with yield return, you'd have to accomplish the same thing by reference.

var asyncOperationHandle = GetMeSomeValueRequest();
yield return asyncOperationHandle;
var someValue = (SomeValue)asyncOperationHandle.Result;
眼眸印温柔 2024-11-02 01:30:18

新的 await 关键字与现有的 yield return 关键字具有相似的语义,因为它们都会导致编译器为您生成延续样式状态机。因此,可以使用与异步 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 将普遍使用,但以不同的方式提供各自的语义。

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. So it is possible to hack something together using iterators that has some of the same behaviors as the 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. All of the magic happens inside the AsyncHelper.Invoke method. 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. 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.

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. The problem is that the coroutine has to actually return an IEnumerable since it is nothing more than a hacked iterator. 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.

如日中天 2024-11-02 01:30:18

await 总是涉及相同类型的转换 - 但这是一个非常痛苦的过程。 await端并不太复杂,但棘手的一点是编译器为您构建一个状态机,允许继续跳回正确的位置。

可能,我对迭代器块(yield return)的一些黑客使用可以伪造类似的东西......但它会非常难看。

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

await always involves the same kind of transformation - but it's a pretty painful one. 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.

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. It may be useful to you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文