9

昨天我开始使用 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;

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

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

4

3 回答 3

16

这里的关键是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);
}

Hello World这将在 5 秒后打印。

于 2011-08-07T08:54:17.123 回答
2

一年后添加

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

这篇采访 Eric Lippert 展示了 async-await 的一个很好的类比。在中间的某处搜索异步等待。

在这篇文章中,非常有帮助的 Eric Lippert 展示了一些关于 async-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>如果您运行的函数的返回值为TResult

您可以只启动任务,执行其他操作,并在需要任务结果时键入等待。有一个缺点:

如果你想“等待”你的函数需要是异步的并返回Task而不是voidTask<TResult>而不是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.StartNew

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

如果您没有任何意义可做,代码将如下所示:

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

注意,SlowAddAsync 被声明为 async 函数,所以使用这个 async 函数的每个人也应该是 async 并返回 Task 或 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();
}

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

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

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

然而,一个单独的函数的好处是你让那些不需要/想要/理解异步的人,可以在没有异步/等待的情况下使用该函数。

记住:

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

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

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

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

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

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

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

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

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

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

有用于 - Internet 访问 - 流读取和写入 - 数据库访问 - 等的异步功能。

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

启动多个任务并等待它们完成 如果您启动多个任务并希望等待所有任务完成,请使用 Task.WhenAll(...)而不是 Task.Wait

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

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

int a = TaskA.Result;

如果其中一项任务引发异常,则将其作为 InnerExceptions 包装在 AggregateException 中。因此,如果您等待 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;
}

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

于 2015-07-29T07:46:01.440 回答
1

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

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());
    }
}
于 2011-08-07T18:52:41.443 回答