2011-08-07 49 views
9

昨天我开始玩微软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<string> task = TaskEx.Run(
      () => 
       { 
        Thread.Sleep(5000); 
        return "Hello World"; 
       }); 

     string str = await task; 

但我会怎么做,与awaitable实施?还是我误解了一切?

感谢任何信息/帮助:)

+1

异步/等待适用于方法,而不适用于任务。 –

+3

@Henk'await'适用于具有合适的'GetAwaiter()'实现的表达式,其中'Task'确实 - 所以'await'适用于'Task'(某种意义上) –

+0

在这种狭义的情况下,等待TaskEx.Delay(5000);',但你必须为你的一般情况研究更多。'await'关键字查找'GetAwaiter',基本上任何提供合适的'GetAwaiter'的东西都可以用于await。 Jon Skeet在他的博客中有一个很棒的系列,详细地等待着。 –

回答

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

+0

那么如何实现一个GetAwaiter实现类? – DavorinP

+1

@Pajci从零开始?我在[这里](http://marcgravell.blogspot.com/2011/04/musings-on-async.html)上发表了博文,并在BookSleeve源文件中留下了一个例子,但我很快就得出结论(http:/ /marcgravell.blogspot.com/2011/04/completion-tasks-easy-way.html),它最好使用TaskCompletionSource - 预先存在***和***比我的版本更快。 –

+0

Upvote使用“burninate”这个词 – brandonstrong

1

我结束了这个示例代码...这是适当的执行awaitable模式?

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

}

+0

使用Async CTP做异步等待Web响应比这更简单。提示:使用'Task.Factory.FromAsync',或者查找名称为'xxxTaskAsync'的方法。 –

+0

但这是等待模式的正确实现吗? – DavorinP

+0

我认为你的服务员需要实现BeginAwait和EndAwait。你真的应该阅读Jon Skeet的博客系列关于自定义的等待者,因为他在写作时提到了大量的问题。 –

1

加入一年后

使用后异步等待了一年多了,我知道,关于异步我在原来的答案写了一些东西是不正确的,尽管答案中的代码仍然正确。赫拉是两个链接,帮助我极大地了解异步等待作品。

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

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

原来的答复

OK,这里是一个完整的例子,在学习过程中帮助了我。

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

当然:使用异步/等待这个,并没有任何设置事件标志和等待这些事件的旧方法设置。

这里是缓慢的计算器:

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

如果你想使用这个异步同时采用异步等待你必须使用Task.Run(...)以异步方式启动它。的Task.Run返回值是一个awaitable任务:

  • Task如果函数的返回值,你运行无效
  • Task<TResult>如果您运行函数的返回值是TResult

你只需启动任务,执行其他操作,并且每当需要输入的任务结果等待时。有一个缺点:

如果你想“等待”你的函数必须是异步和返回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; 
} 

边注:有些人喜欢Task.Factory.StartNew以上Start.Run。看看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>:

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

约异步的好处/等待是,你不必与ContinueWith摆弄等到前面的任务就完成了。只需使用await,并且您知道任务已完成并且您具有返回值。等待之后的陈述是你在ContinueWith中通常要做的事情。

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

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

但是一个单独的函数的好处是,你给那些不需要/想要/理解异步的人,可以使用没有异步/等待的功能。

记住:

使用的await应该是异步

每个异步函数返回任务或任务<Tresult每个函数>

“但我的事件处理程序不能返回任务!“

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

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

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

所以点击该按钮时,异步事件处理程序将继续UI响应:

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

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

许多.NET函数都有异步版本,它们返回任务或任务<TResult>。

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

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

启动多项任务,并等待他们完成 如果你开始的几个任务,要等到所有的人来完成,使用Task.WhenAll(...)不Task.Wait

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

一旦任务完成,返回值已经是await的返回值,但是如果您等待Task.WhenAll(new Task [] {TaskA,TaskB,TaskC}); 您必须使用任务<TResult>。结果财产知道任务的结果:

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

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