编写自己的异步方法

时间:2014-07-25 10:37:32

标签: c# .net asynchronous task-parallel-library async-await

我想知道如何编写自己的异步方法"更正"方式。

我见过很多帖子解释async / await模式,如下所示:

http://msdn.microsoft.com/en-us/library/hh191443.aspx

// Three things to note in the signature: 
//  - The method has an async modifier.  
//  - The return type is Task or Task<T>. (See "Return Types" section.)
//    Here, it is Task<int> because the return statement returns an integer. 
//  - The method name ends in "Async."
async Task<int> AccessTheWebAsync()
{ 
    // You need to add a reference to System.Net.Http to declare client.
    HttpClient client = new HttpClient();

    // GetStringAsync returns a Task<string>. That means that when you await the 
    // task you'll get a string (urlContents).
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoIndependentWork();

    // The await operator suspends AccessTheWebAsync. 
    //  - AccessTheWebAsync can't continue until getStringTask is complete. 
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync. 
    //  - Control resumes here when getStringTask is complete.  
    //  - The await operator then retrieves the string result from getStringTask. 
    string urlContents = await getStringTask;

    // The return statement specifies an integer result. 
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value. 
    return urlContents.Length;
}

private void DoIndependentWork()
{
    resultsTextBox.Text += "Working........\r\n";
}

这适用于已经实现此功能的任何.NET方法,如

  • System.IO opertions
  • DataBase opertions
  • 网络相关操作(下载,上传......)

但是如果我想编写自己的方法需要花费相当长的时间来完成哪些方法我可以使用并且重负载在上面示例的DoIndependentWork方法中呢?

在这种方法中,我可以这样做:

  • 字符串操作
  • 计算
  • 处理我自己的对象
  • 汇总,比较,过滤,分组,处理内容
  • 列出操作,添加,删除,处理

我再次偶然发现很多帖子,人们只是做了以下事情(再次采用上面的例子):

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    await DoIndependentWork();

    string urlContents = await getStringTask;

    return urlContents.Length;
}

private Task DoIndependentWork()
{
    return Task.Run(() => {

        //String manipulations
        //Calculations
        //Handling my own objects
        //Aggregating, comparing, filtering, grouping, handling stuff
        //List operations, adding, removing, coping
    });
}

您可能会注意到更改是DoIndependentWork现在返回任务,并且在AccessTheWebAsync任务中该方法获得了await

重载操作现在封装在Task.Run()内,这是全部吗? 如果只需要为我的库中的每个方法提供异步方法,我需要做的就是:

public class FooMagic
{
    public void DoSomeMagic()
    {
        //Do some synchron magic...
    }

    public Task DoSomeMagicAsync()
    {
        //Do some async magic... ?!?
        return Task.Run(() => { DoSomeMagic(); });
    }
}

如果你能解释一下,即使是像这样的高投票问题,也会很好: How to write simple async method?只用已经存在的方法解释它,只使用asyn / await模式,就像上面提到的问题的评论一样: How to write simple async method?

3 个答案:

答案 0 :(得分:24)

  

如果您能向我解释一下会很好:如何编写简单的异步   方法

首先,我们需要了解async方法的含义。如果向使用async方法的最终用户公开async方法,您就会告诉他:&#34;聆听,此方法将通过承诺快速返回给您在不久的将来某个时间完成&#34;。这就是您向用户提供的保证。

现在,我们需要了解Task如何做到这一点&#34;承诺&#34;可能。正如您在问题中提出的那样,为什么只在我的方法中添加Task.Run会使用await关键字等待它有效?

Task实现GetAwaiter模式,这意味着它返回一个名为awaiter的对象(实际上称为TaskAwaiter)。 TaskAwaiter对象实现INotifyCompletionICriticalNotifyCompletion接口,公开OnCompleted方法。

使用await关键字后,编译器会反过来使用所有这些好东西。编译器将确保在设计时,您的对象实现GetAwaiter,然后使用它将代码编译到状态机中,这将使您的程序能够在等待时将控制权交还给调用者,并恢复当这项工作完成时。

现在,有一些准则要遵循。一个真正的异步方法并没有在幕后使用额外的线程来完成它的工作(Stephan Cleary在There Is No Thread中对此进行了很好的解释),这意味着暴露一个在内部使用Task.Run的方法有点误导对于你的api的消费者,因为他们不会假设你的任务中没有额外的线程。您应该做的是同步公开您的API,并让用户使用Task.Run自己卸载它,控制执行流程。

async方法主要用于 I / O Bound 操作,因为这些操作在执行IO操作时自然不需要消耗任何线程,这是为什么我们在负责执行IO操作的类中看到它们,例如硬盘驱动器调用,网络呼叫等。

我建议阅读Parallel PFX团队的文章Should I expose asynchronous wrappers for synchronous methods?,该文章准确地说明了您尝试做什么以及为什么不建议。

答案 1 :(得分:23)

实际答案

您使用TaskCompletionSource执行此操作,Promise TaskStephen Toub's AsyncManualResetEvent不执行任何代码且仅执行:

  

“表示未绑定到委托的任务的生产者端,通过Task属性提供对使用者端的访问。”

在启动异步操作时将该任务返回给调用者,并在结束时设置结果(或异常/取消)。确保操作真的是异步的就在你身上。

以下是synchronization constructs实现中所有异步方法的根的一个很好的示例:

class AsyncManualResetEvent 
{ 
    private volatile TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();

    public Task WaitAsync() { return _tcs.Task; } 
    public void Set() { _tcs.TrySetResult(true); } 
    public void Reset() 
    { 
        while (true) 
        { 
            var tcs = _tcs; 
            if (!tcs.Task.IsCompleted || 
                Interlocked.CompareExchange(ref _tcs, new TaskCompletionSource<bool>(), tcs) == tcs) 
                return; 
        } 
    } 
}

背景

使用async-await基本上有两个原因:

  1. 改进的可伸缩性:当您进行I/O密集型工作(或其他固有的异步操作)时,您可以异步调用它,这样您就可以释放调用线程并且能够执行其他工作同时。
  2. 卸载:当您进行CPU密集工作时,可以异步调用它,这会将工作从一个线程移到另一个线程(主要用于GUI个线程)
  3. 因此,大多数。Net框架的异步调用支持async开箱即用,卸载时使用Task.Run(如您的示例所示)。实际上需要实现async的唯一情况是您创建新的异步调用(例如I/O或异步The Nature of TaskCompletionSource)。

    这些案件非常罕见,这就是为什么你大多找到答案

      

    “仅使用现有方法解释它,只使用async/await模式”


    您可以更深入地了解{{3}}

答案 2 :(得分:13)

TL; DR:

Task.Run()是您想要的,但要小心将其隐藏在您的库中。

我可能错了,但你可能正在getting CPU-Bound code to run asynchronously寻找指导[Stephen Cleary]。我也很难找到这个,我认为它之所以如此困难,是因为它不是你应该为图书馆所做的 - 有点......

文章

链接的文章是一个很好的阅读(5-15分钟,取决于),它详细介绍了如何使用Task.Run()作为API的一部分而不是使用它来阻止a UI线程 - 并区分两种&#34;类型&#34;人们喜欢异步运行的长时间运行过程:

  • 受CPU限制的进程(一个正在处理/实际工作并需要一个单独的线程来完成其工作)
  • 真正的异步操作(有时称为IO绑定 - 在这里和那里做一些事情,在操作之间有一堆等待时间,最好不要在线程中占用一些线程#39;坐在那里什么都不做。)

本文涉及在各种环境中使用API​​函数,并解释相关的架构是否更喜欢&#34;同步或异步方法,以及具有同步和异步方法签名的API&#34;看起来&#34;给开发者。

答案

最后一节&#34; 好的,关于错误的解决方案呢?我们如何以正确的方式解决这个问题?&#34;进入我认为你所问的问题,结束于此:

  

结论:在方法的实现中不要使用Task.Run;相反,使用Task.Run来调用方法。

基本上,Task.Run()&#39; hogs&#39;一个线程,因此是用于CPU绑定工作的东西,但它归结为使用它。如果您正在尝试执行需要大量工作而又不想阻止UI线程的事情,请使用Task.Run()直接运行功能 >(即在事件处理程序或基于UI的代码中):

class MyService
{
  public int CalculateMandelbrot()
  {
    // Tons of work to do in here!
    for (int i = 0; i != 10000000; ++i)
      ;
    return 42;
  }
}

...

private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => myService.CalculateMandelbrot());
}

但是...... 不要隐藏Task.Run()在API函数后缀-Async中,如果它是一个CPU绑定函数,基本上每个{{ 1}}函数是真正的异步,而不是CPU绑定。

-Async

换句话说,不要调用CPU绑定函数// Warning: bad code! class MyService { public int CalculateMandelbrot() { // Tons of work to do in here! for (int i = 0; i != 10000000; ++i) ; return 42; } public Task<int> CalculateMandelbrotAsync() { return Task.Run(() => CalculateMandelbrot()); } } ,因为用户会认为它是IO绑定的 - 只需使用-Async异步调用它,并让其他用户执行当他们认为合适的时候也一样。或者,将其命名为对您有意义的其他内容(可能是Task.Run()BeginAsyncIndependentWork())。