编写自己的异步方法

Ran*_*dom 42 .net c# 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";
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是,如果我想编写自己的方法需要花费相当长的时间来完成哪些方法我可以使用并且重负载在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
    });
}
Run Code Online (Sandbox Code Playgroud)

您可能会注意到这些更改DoIndependentWork现在返回一个Task,并且在该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(); });
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你能解释一下,即使这样的高投票问题:如何编写简单的异步方法,也会很好 只用已经存在的方法解释它,并且只使用asyn/await模式,就像这个提到的问题的注释带来的那样: 如何编写简单的异步方法?

i3a*_*non 25

实际答案

你这样做TaskCompletionSource,它有一个Promise Task,不执行任何代码,只有:

"表示未绑定到委托的Task的生产者端,通过Task属性提供对使用者端的访问."

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

这是Stephen Toub实现中所有异步方法的这种根的一个很好的例子:AsyncManualResetEvent

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; 
        } 
    } 
}
Run Code Online (Sandbox Code Playgroud)

背景

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

  1. 改进的可伸缩性:当您进行I/O密集工作(或其他固有的异步操作)时,您可以异步调用它,这样您就可以释放调用线程,并且它能够同时执行其他工作.
  2. 卸载:当您进行CPU密集工作时,可以异步调用它,这会将工作从一个线程移到另一个线程(主要用于GUI线程).

所以大部分都是.Net框架的异步调用支持async开箱即用和卸载使用Task.Run(如在您的示例中).实际需要实现async自己的唯一情况是创建新的异步调用(例如I/O异步同步构造).

这些案件极为罕见,这就是为什么你大多找到答案的原因

"只用已有的方法解释它,只使用async/await模式"


您可以深入了解TaskCompletionSource的本质

  • 这应该是公认的答案;) (4认同)

Yuv*_*kov 24

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

首先,我们需要了解async方法的含义.当一个async方法向使用该async方法的最终用户公开一个方法时,你告诉他:"听着,这种方法将很快回复给你,承诺在不久的将来某个时候完成".这就是您向用户保证的.

现在,我们需要了解如何Task使这个"承诺"成为可能.正如您在问题中提出的那样,为什么简单地Task.Run在我的方法中添加内容使得使用await关键字等待它是有效的?

A Task实现GetAwaiter模式,意味着它返回一个名为awaiter(实际上被称为TaskAwaiter)的对象.该TaskAwaiter对象实现INotifyCompletionICriticalNotifyCompletion接口,暴露OnCompleted方法.

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

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

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

我建议阅读Parallel PFX团队文章我应该为同步方法公开异步包装器吗?它准确地说明了你想要做什么以及为什么不推荐它.

  • 我对答案并不是很满意,因为我错过了"你怎么写它"的答案我只得到"怎么不写",也许你可以提供一些代码示例,说明如何编写一个简单的异步方法?我更关注卸载而不是可伸缩性,因为我的项目是一个Entity Framework项目,EF它自己不支持带有1个上下文的多异步操作. (9认同)

Cod*_*key 14

TL; DR:

Task.Run() 是你想要的,但要小心将它藏在你的图书馆.

我可能是错的,但你可能正在寻找关于让CPU-Bound代码异步运行的指导[由Stephen Cleary].我也很难找到这个,而且我认为它之所以如此困难,是因为它不是你应该为图书馆做的 - 有点......

文章

链接的文章是一个很好的阅读(5-15分钟,取决于),Task.Run()其中涉及使用它作为API的一部分的hows和为什么使用它来阻止UI线程 - 并区分两个"类型"人们喜欢异步运行的长时间运行的进程:

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

本文讨论了在各种上下文中使用API​​函数,并解释了相关体系结构"更喜欢"同步或异步方法,以及具有同步和异步方法签名的API如何"看起来"给开发人员.

回答

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

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

基本上,Task.Run()"猪"一个线程,因此使用了CPU限制的工作的事情,但它归结为在那里它的使用.当你试图做一些事情,需要大量的工作,你不想阻止用户界面线程,使用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());
}
Run Code Online (Sandbox Code Playgroud)

但是...... 如果它是一个CPU绑定函数,不要隐藏你Task.Run()的API函数后缀-Async,因为基本上每个-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());
  }
}
Run Code Online (Sandbox Code Playgroud)

换句话说,不要调用CPU绑定函数-Async,因为用户会认为它是IO绑定的 - 只是使用异步调用它Task.Run(),并让其他用户在他们认为合适的时候也这样做.或者,将其命名为对您有意义的其他内容(可能BeginAsyncIndependentWork()或者StartIndependentWorkTask()).