如何使任务可等待

发布于 2024-11-28 10:41:39 字数 823 浏览 2 评论 0 原文

昨天我开始使用 Microsoft CTP 异步库,但我找不到可等待任务的正确实现。我知道它必须有这样的实现?:

public struct SampleAwaiter<T>
{
    private readonly Task<T> task;
    public SampleAwaiter(Task<T> task) { this.task = task; }
    public bool IsCompleted { get { return task.IsCompleted; } }
    public void OnCompleted(Action continuation) { TaskEx.Run(continuation); }
    public T GetResult() { return task.Result; }
}

但是我现在如何实现一个任务,比方说,等待 5 秒,然后返回一些字符串,例如“Hello World”?

一种方法是直接使用 Task,如下所示:

Task<string> task = TaskEx.Run(
            () =>
                {
                    Thread.Sleep(5000);
                    return "Hello World";
                });

        string str = await task;

但是我将如何使用可等待的实现来做到这一点?或者我只是误解了一切?

感谢您提供任何信息/帮助:)

Yesterday I started playing with Microsoft CTP async library, and nowhere I could not find the proper implementation of the awaitable Task. I know that it must have implementation like this?:

public struct SampleAwaiter<T>
{
    private readonly Task<T> task;
    public SampleAwaiter(Task<T> task) { this.task = task; }
    public bool IsCompleted { get { return task.IsCompleted; } }
    public void OnCompleted(Action continuation) { TaskEx.Run(continuation); }
    public T GetResult() { return task.Result; }
}

But how would I now implement a task that would, let's say, wait 5 seconds, and the return some string, for example "Hello World"?

One way is to use Task directly like so:

Task<string> task = TaskEx.Run(
            () =>
                {
                    Thread.Sleep(5000);
                    return "Hello World";
                });

        string str = await task;

But how would I do that with the awaitable implementation? Or did I just misunderstood everything?

Thanks for any information/help :)

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

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

发布评论

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

评论(3

迷爱 2024-12-05 10:41:39

这里的关键是 AsyncCtpThreadingExtensions.GetAwaiter 它通过扩展方法提供这些方法。由于异步实现是基于模式的(如LINQ),而不是绑定到特定的接口,它可以来自任何地方(在本例中是TaskAwaiter)。

您编写的代码值得等待。例如:

static void Main()
{
    Test();
    Console.ReadLine(); // so the exe doesn't burninate
}
static async void Test() {
    Task<string> task = TaskEx.Run(
           () =>
           {
               Thread.Sleep(5000);
               return "Hello World";
           });
    string str = await task;
    Console.WriteLine(str);
}

5 秒后打印 Hello World

The key here is AsyncCtpThreadingExtensions.GetAwaiter which provides those methods via an extension method. Since the async implementation is pattern based (like LINQ), rather than tied to a specific interface it can come from everywhere (it is TaskAwaiter in this case).

Your code as written is awaitable. For example:

static void Main()
{
    Test();
    Console.ReadLine(); // so the exe doesn't burninate
}
static async void Test() {
    Task<string> task = TaskEx.Run(
           () =>
           {
               Thread.Sleep(5000);
               return "Hello World";
           });
    string str = await task;
    Console.WriteLine(str);
}

This prints Hello World after 5 seconds.

半枫 2024-12-05 10:41:39

一年后添加

使用 async-await 一年多了,我知道我在原来的答案中写的关于 async 的一些内容不正确,尽管答案中的代码仍然是正确的。 Hera 有两个链接极大地帮助我理解了 async-await 的工作原理。

这次采访 Eric Lippert 展示了 async-await 的一个很好的类比。在中间的某个位置搜索 async-await。

在本文中,非常乐于助人的 Eric Lippert 展示了一些异步的良好实践-await

原始答案

好的,这是一个在学习过程中对我有帮助的完整示例。

假设您有一个速度较慢的计算器,并且您想在按下按钮时使用它。同时,您希望您的 UI 保持响应能力,甚至可能做其他事情。当计算器完成后,您想要显示结果。

当然:为此使用 async/await,而不是像设置事件标志和等待设置这些事件这样的旧方法。

这是慢速计算器:

private int SlowAdd(int a, int b)
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a+b;
}

如果您想在使用 async-await 时异步使用它,则必须使用 Task.Run(...) 异步启动它。 Task.Run 的返回值是一个可等待的任务:

  • Task 如果您运行的函数的返回值为 void
  • Task 如果您运行的函数的返回值是 TResult

您可以启动任务,执行其他操作,并且每当您需要任务的结果时,您可以键入await。有一个缺点:

如果您想“等待”,您的函数需要异步并返回 Task 而不是 voidTask而不是 TResult

这是运行慢速计算器的代码。
使用 async 终止异步函数的标识符是常见的做法。

private async Task<int> SlowAddAsync(int a, int b)
{
    var myTask = Task.Run ( () => SlowAdd(a, b));
    // if desired do other things while the slow calculator is working
    // whenever you have nothing to do anymore and need the answer use await
    int result = await myTask;
    return result;
}

旁注:有些人更喜欢 Task.Factory.StartNew 而不是 Start.Run。看看 MSDN 对此有何介绍:

MSDN:Task.Run 与 Task.Factory .开始新的

SlowAdd 作为异步函数启动,并且您的线程继续。一旦它需要答案,它就会等待任务。返回值是 TResult,在本例中是一个 int。

如果您没有什么有意义的事情要做,代码将如下所示:

private async Task`<int`> SlowAddAsync(int a, int b)
{
    return await Task.Run ( () => SlowAdd(a, b));
}

请注意,SlowAddAsync 被声明为异步函数,因此使用此异步函数的每个人也应该是异步的并返回 Task 或 Task> ;:

private async Task UpdateForm()
{
     int x = this.textBox1.Text;
     int y = this.textBox2.Text;
     int sum = await this.SlowAddAsync(x, y);
     this.label1.Text = sum.ToString();
}

async/await 的好处是你不必摆弄ContinueWith 来等待上一个任务完成。只需使用await,您就知道任务已完成并且您有返回值。 wait 之后的语句是您通常在ContinueWith 中执行的操作。

顺便说一句,你的 Task.Run 不必调用函数,你也可以在其中放置一个语句块:

int sum = await Task.Run( () => {
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a+b});

然而,单独函数的好处是,你可以为那些不需要/想要/理解异步的人提供帮助,可以在没有 async/await 的情况下使用该函数。

记住:

每个使用await的函数都应该是异步的

每个异步函数都应返回 Task 或 Task>


“但是我的事件处理程序无法返回任务!”

private void OnButton1_Clicked(object sender, ...){...}

你是对的,因此这是唯一的例外:

异步事件处理程序可能返回 void

因此,当单击按钮时,异步事件处理程序将使 UI 保持响应:

private async void OnButton1_Clicked(object sender, ...)
{
    await this.UpdateForm();
}

但是,您仍然必须声明事件处理程序异步

许多 .NET 函数都有返回任务或任务> 的异步版本。


有异步函数
- 互联网接入
- 流读写
- 数据库访问
- 等等。

要使用它们,您不必调用 Task.Run,​​它们已经返回 Task 和 Task>只需打电话给他们,继续做你自己的事情,当你需要等待任务的答案并使用 TResult 时。

启动多项任务并等待它们完成
如果您启动多个任务并且想要等待所有任务完成,请使用 Task.WhenAll(...) NOT Task.Wait

Task.Wait 返回 void。 Task.WhenAll 返回一个 Task,因此您可以等待它。

一旦一个任务执行完毕,返回值已经是await的返回了,但是如果你await Task.WhenAll(new Task[]{TaskA, TaskB, TaskC});
您必须使用 Task>.Result 属性来了解任务的结果:

int a = TaskA.Result;

如果其中一个任务引发异常,则会将其包装为 AggregateException 中的 InnerException。因此,如果您等待 Task.WhenAll,请准备好捕获 AggregateException 并检查 innerExceptions 以查看您启动的任务引发的所有异常。使用 AggregateException.Flatten 函数可以更轻松地访问异常。

有关取消的有趣阅读:

MSDN 关于托管中的取消线程

最后:您使用 Thread.Sleep(...)。异步版本是 Task.Delay(TimeSpan):

private async Task`<int`> MySlowAdd(int a, int b)
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    return a+b;
}

如果您使用此函数,您的程序将保持响应。

Addition one year later

After using async-await for over a year now, I know that some things about async I wrote in my original answer are not correct, although the code in the answer is still correct. Hera are two links that helped me enormously to understand how async-await works.

This interview Eric Lippert shows an excellent analogy for async-await. Search somewhere in the middle for async-await.

In this article, the ever so helpful Eric Lippert shows some good practices for async-await

Original answer

OK, here is a full example that helped me during the learning process.

Suppose you have a slow calculator, and you want to use it when pressing a button. Meanwhile you want your UI to stay responsive, and maybe even do other things. When the calculator is finished you want to display the result.

And of course: use async / await for this, and none of the old methods like setting event flags and waiting for these events to be set.

Here is the slow calculator:

private int SlowAdd(int a, int b)
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a+b;
}

If you want to use this asynchronously while using async-await you have to use Task.Run(...) to start it asynchronously. The return value of Task.Run is an awaitable Task:

  • Task if the return value of the function you Run is void
  • Task<TResult> if the return value of the function you run is TResult

You can just start the Task, do something else, and whenever you need the result of the Task you type await. There is one drawback:

If you want to 'await' your function needs to be async and return Task instead of void or Task<TResult> instead of TResult.

Here's the code that runs the slow calculator.
It's common practice to terminate the identifier of an async function with async.

private async Task<int> SlowAddAsync(int a, int b)
{
    var myTask = Task.Run ( () => SlowAdd(a, b));
    // if desired do other things while the slow calculator is working
    // whenever you have nothing to do anymore and need the answer use await
    int result = await myTask;
    return result;
}

Side remark: Some people prefer Task.Factory.StartNew above Start.Run. See what MSDN tells about this:

MSDN: Task.Run versus Task.Factory.StartNew

The SlowAdd is started as an async function, and your thread continues. Once it needs the answer it awaits for the Task. The return value is the TResult, which in this case is an int.

If you have nothing meaningful to do the code would look like this:

private async Task`<int`> SlowAddAsync(int a, int b)
{
    return await Task.Run ( () => SlowAdd(a, b));
}

Note that SlowAddAsync is declared an async function, so everyone who uses this async function should also be async and return Task or Task<TResult>:

private async Task UpdateForm()
{
     int x = this.textBox1.Text;
     int y = this.textBox2.Text;
     int sum = await this.SlowAddAsync(x, y);
     this.label1.Text = sum.ToString();
}

The nice thing about async / await is that you don't have to fiddle with ContinueWith to wait until the previous task is finished. Just use await, and you know the task is finished and you have the return value. The statement after the await is what you'd normally do in the ContinueWith.

By the way, your Task.Run doesn't have to call a function, you can also put a statement block in it:

int sum = await Task.Run( () => {
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return a+b});

However the nice thing about a separate function is that you give those who don't need / want / understand async, the possibility to use the function without async / await.

Remember:

Every function that uses await should be async

Every async function should return Task or Task<Tresult>

"But my event handler can't return a Task!"

private void OnButton1_Clicked(object sender, ...){...}

You're right, therefore that is the only exception:

async event handlers may return void

So when clicking the button, the async event handler would keep the UI responsive:

private async void OnButton1_Clicked(object sender, ...)
{
    await this.UpdateForm();
}

However you still have to declare the event handler async

A lot of .NET functions have async versions that return a Task or Task<TResult>.

There are async functions for
- Internet access
- Stream Read and Write
- Database access
- etc.

To use them you don't have to call Task.Run, they already return Task and Task<TResult> just call them, continue doing your own stuff and when you need the answer await for the Task and use the TResult.

Start several tasks and wait for them to finish
If you start several tasks and you want to wait for all of them to finish, use Task.WhenAll(...) NOT Task.Wait

Task.Wait returns a void. Task.WhenAll returns a Task, so you can await for it.

Once a task is finished, the return value is already the return of the await, but if you await Task.WhenAll ( new Task[]{TaskA, TaskB, TaskC});
you have to use the Task<TResult>.Result property to know the result of a task:

int a = TaskA.Result;

If one of the tasks throws an exception, it is wrapped as InnerExceptions in an AggregateException. So if you await Task.WhenAll, be prepared to catch the AggregateException and check the innerExceptions to see all exceptions thrown by the tasks you started. Use the function AggregateException.Flatten to access the exceptions more easily.

Interesting to read about cancellation:

MSDN about Cancellation in managed threads

Finally: you use Thread.Sleep(...). The async version is Task.Delay(TimeSpan):

private async Task`<int`> MySlowAdd(int a, int b)
{
    await Task.Delay(TimeSpan.FromSeconds(5));
    return a+b;
}

If you use this function your program keeps responsive.

瘫痪情歌 2024-12-05 10:41:39

我最终得到了这个示例代码......这是可等待模式的正确实现吗?

namespace CTP_Testing
{
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class CustomAsync
{
    public static CustomAwaitable GetSiteHeadersAsync(string url)
    {
        return new CustomAwaitable(url);
    }
}

public class CustomAwaitable
{
    private readonly Task<string> task;
    private readonly SynchronizationContext ctx;

    public CustomAwaitable(string url)
    {
        ctx = SynchronizationContext.Current;
        this.task = Task.Factory.StartNew(
            () =>
                {
                    var req = (HttpWebRequest)WebRequest.Create(url);
                    req.Method = "HEAD";
                    var resp = (HttpWebResponse)req.GetResponse();
                    return this.FormatHeaders(resp.Headers);
                });
    }
    public CustomAwaitable GetAwaiter() { return this; }
    public bool IsCompleted { get { return task.IsCompleted; } }
    public void OnCompleted(Action continuation)
    {
        task.ContinueWith(_ => ctx.Post(delegate { continuation(); }, null));
    }
    public string GetResult() { return task.Result; }

    private string FormatHeaders(WebHeaderCollection headers)
    {
        var headerString = headers.Keys.Cast<string>().Select(
            item => string.Format("{0}: {1}", item, headers[item]));

        return string.Join(Environment.NewLine, headerString.ToArray());
    }
}

I ended up with this sample code ... is this the proper implementation of the awaitable pattern?

namespace CTP_Testing
{
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class CustomAsync
{
    public static CustomAwaitable GetSiteHeadersAsync(string url)
    {
        return new CustomAwaitable(url);
    }
}

public class CustomAwaitable
{
    private readonly Task<string> task;
    private readonly SynchronizationContext ctx;

    public CustomAwaitable(string url)
    {
        ctx = SynchronizationContext.Current;
        this.task = Task.Factory.StartNew(
            () =>
                {
                    var req = (HttpWebRequest)WebRequest.Create(url);
                    req.Method = "HEAD";
                    var resp = (HttpWebResponse)req.GetResponse();
                    return this.FormatHeaders(resp.Headers);
                });
    }
    public CustomAwaitable GetAwaiter() { return this; }
    public bool IsCompleted { get { return task.IsCompleted; } }
    public void OnCompleted(Action continuation)
    {
        task.ContinueWith(_ => ctx.Post(delegate { continuation(); }, null));
    }
    public string GetResult() { return task.Result; }

    private string FormatHeaders(WebHeaderCollection headers)
    {
        var headerString = headers.Keys.Cast<string>().Select(
            item => string.Format("{0}: {1}", item, headers[item]));

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