如何使任务等待

时间:2011-08-07 08:48:15

标签: c# async-ctp async-await

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

但是如何通过等待实施来做到这一点?或者我只是误解了一切?

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

3 个答案:

答案 0 :(得分: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);
}

5秒后打印Hello World

答案 1 :(得分:2)

一年后加入

在使用async-await一年多之后,我知道我在原始答案中写的有关async的一些事情是不正确的,尽管答案中的代码仍然是正确的。 Hera是两个链接,帮助我非常了解async-await如何工作。

This interview Eric Lippert shows an excellent analogy for async-await。在中间某处搜索async-await。

In this article, the ever so helpful Eric Lippert shows some good practices for 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

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

如果你想等待&#39;您的函数需要异步并返回Task而不是voidTask<TResult>而不是TResult

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

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

旁注:有些人更喜欢Start.Run上方的Task.Factory.StartNew。了解MSDN对此的描述:

     

MSDN: Task.Run versus Task.Factory.StartNew

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

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

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

请注意,SlowAddAsync被声明为异步函数,因此使用此异步函数的每个人也应该是异步并返回任务或任务<TResult&gt;:

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,您就知道任务已完成并且您具有返回值。等待之后的声明是您在ContinueWith中通常所做的事情。

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

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

然而,关于单独功能的好处是你给那些不需要/想要/理解异步的人,没有async / await就可以使用这个功能。

记住:

  

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

     

每个异步函数都应返回任务或任务<Tresult&gt;

&#34;但我的事件处理程序无法返回任务!&#34;

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

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

  

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

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

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

但是,您仍然需要声明事件处理程序async

  

许多.NET函数都有异步版本,可以返回任务或任务<TResult&gt;。

有异步功能  - 互联网   - 流读和写   - 数据库访问   - 等等。

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

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

Task.Wait返回一个void。 Task.WhenAll返回一个Task,所以你可以等待它。

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

int a = TaskA.Result;

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

有兴趣阅读有关取消的内容:

MSDN about Cancellation in managed threads

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

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

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

答案 2 :(得分: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());
    }
}

}