简体   繁体   English

编写自己的异步方法

[英]Write your own async method

I would like to know how to write your own async methods the "correct" way. 我想知道如何以“正确”的方式编写自己的异步方法。

I have seen many many posts explaining the async/await pattern like this: 我看过许多帖子解释async / await模式,如下所示:

http://msdn.microsoft.com/en-us/library/hh191443.aspx 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";
}

This works great for any .NET Method that already implements this functionality like 这适用于已经实现此功能的任何.NET方法

  • System.IO opertions System.IO操作
  • DataBase opertions DataBase操作
  • Network related operations (downloading, uploading...) 网络相关操作(下载,上传......)

But what if I want to write my own method that takes quite some time to complete where there just is no Method I can use and the heavy load is in the DoIndependentWork method of the above example? 但是如果我想编写我自己的方法需要花费很长时间来完成哪里没有我可以使用的方法,并且重负载在上面例子的DoIndependentWork方法中呢?

In this method I could do: 在这种方法中,我可以这样做:

  • String manipulations 字符串操作
  • Calculations 计算
  • Handling my own objects 处理我自己的对象
  • Aggregating, comparing, filtering, grouping, handling stuff 聚合,比较,过滤,分组,处理内容
  • List operations, adding, removing, coping 列出操作,添加,删除,应对

Again I have stumbled across many many posts where people just do the following (again taking the above example): 我再次偶然发现许多帖子,人们只是做了以下事情(再次采用上面的例子):

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

You may notice that the changes are that DoIndependentWork now returns a Task and in the AccessTheWebAsync task the method got an await . 您可能会注意到更改是DoIndependentWork现在返回一个任务,并且在AccessTheWebAsync任务中,该方法得到了await

The heavy load operations are now capsulated inside a Task.Run() , is this all it takes? 重载操作现在封装在Task.Run() ,这需要它吗? If that's all it takes is the only thing I need to do to provide async Method for every single method in my library the following: 如果只需要为我的库中的每个方法提供异步方法,我需要做的就是:

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

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

Would be nice if you could explain it to me since even a high voted question like this: How to write simple async method? 如果你能解释一下,即使这样的高投票问题: 如何编写简单的异步方法,也会很好 only explains it with already existing methods and just using asyn/await pattern like this comment of the mentioned question brings it to the point: How to write simple async method? 只用已经存在的方法解释它,并且只使用asyn / await模式,就像这个提到的问题的注释带来的那样: 如何编写简单的异步方法?

Actual Answer 实际答案

You do that using TaskCompletionSource , which has a Promise Task that doesn't execute any code and only: 你可以使用TaskCompletionSource做到这TaskCompletionSource ,它有一个不执行任何代码的Promise Task ,只有:

"Represents the producer side of a Task unbound to a delegate, providing access to the consumer side through the Task property." “表示未绑定到委托的Task的生产者端,通过Task属性提供对使用者端的访问。”

You return that task to the caller when you start the asynchronous operation and you set the result (or exception/cancellation) when you end it. 在启动异步操作时将该任务返回给调用者,并在结束时设置结果(或异常/取消)。 Making sure the operation is really asynchronous is on you. 确保操作真的是异步的就在你身上。

Here is a good example of this kind of root of all async method in Stephen Toub's AsyncManualResetEvent implementation: 这是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; 
        } 
    } 
}

Background 背景

There are basically two reasons to use async-await : 使用async-await基本上有两个原因:

  1. Improved scalability : When you have I/O intensive work (or other inherently asynchronous operations), you can call it asynchronously and so you release the calling thread and it's capable of doing other work in the mean time. 改进的可伸缩性 :当您进行I/O密集型工作(或其他固有的异步操作)时,您可以异步调用它,这样您就可以释放调用线程,并且它能够同时执行其他工作。
  2. Offloading: When you have CPU intensive work, you can call it asynchronously, which moves the work off of one thread to another (mostly used for GUI threads). 卸载:当您有CPU密集型工作时,可以异步调用它,这会将工作从一个线程移到另一个线程(主要用于GUI线程)。

So most of the . 所以大部分都是。 Net framework's asynchronous calls support async out of the box and for offloading you use Task.Run (as in your example). Net框架的异步调用支持async开箱即用,卸载时使用Task.Run (如示例所示)。 The only case where you actually need to implement async yourself is when you create a new asynchronous call ( I/O or async synchronization constructs for example). 您实际需要自己实现async的唯一情况是创建新的异步调用(例如I/O或异步同步构造 )。

These cases are extremely rare, which is why you mostly find answers that 这些案件极为罕见,这就是为什么你大多找到答案的原因

"Only explains it with already existing methods and just using async/await pattern" “只用已有的方法解释它,只使用async/await模式”


You can go deeper in The Nature of TaskCompletionSource 您可以深入了解TaskCompletionSource的本质

Would be nice if you could explain it to me: How to write simple async method? 如果你能向我解释一下会很好:如何编写简单的异步方法?

First, we need to understand what an async method means. 首先,我们需要了解async方法的含义。 When one exposes an async method to the end user consuming the async method, you're telling him: "Listen, this method will return to you quickly with a promise of completing sometime in the near future". 当一个人暴露的async方法向最终用户消费的async方法,你告诉他:“听着,此方法将迅速恢复到你在不久的将来的某个时候完成的承诺 。” That is what you're guaranteeing to your users. 这就是您向用户保证的。

Now, we need to understand how Task makes this "promise" possible. 现在,我们需要了解Task如何使这个“承诺”成为可能。 As you ask in your question, why simply adding a Task.Run inside my method makes it valid to be awaited using the await keyword? 正如您在问题中提出的那样,为什么只在我的方法中添加Task.Run会使用await关键字await它有效?

A Task implements the GetAwaiter pattern, meaning it returns an object called an awaiter (Its actually called TaskAwaiter ). Task实现了GetAwaiter模式,这意味着它返回一个名为awaiter的对象(实际上称为TaskAwaiter )。 The TaskAwaiter object implements either INotifyCompletion or ICriticalNotifyCompletion interfaces, exposing a OnCompleted method. TaskAwaiter对象实现INotifyCompletionICriticalNotifyCompletion接口,公开OnCompleted方法。

All these goodies are in turn used by the compiler once the await keyword is used. 一旦使用了await关键字,编译器就会使用所有这些好东西。 The compiler will make sure that at design time, your object implements GetAwaiter , and in turn use that to compile the code into a state machine, which will enable your program to yield control back to the caller once awaited, and resume when that work is completed. 编译器将确保在设计时,您的对象实现GetAwaiter ,然后使用它将代码编译到状态机中,这将使您的程序在等待时将控制权交还给调用者,并在该工作时恢复完成。

Now, there are some guidelines to follow. 现在,有一些准则可供遵循。 A true async method doesn't use extra threads behind the scenes to do its job (Stephan Cleary explains this wonderfully in There Is No Thread ), meaning that exposing a method which uses Task.Run inside is a bit misleading to the consumers of your api, because they will assume no extra threading involved in your task. 一个真正的异步方法不会在幕后使用额外的线程来完成它的工作(Stephan Cleary在“没有线程”中解释了这一点),这意味着暴露一个使用Task.Run的方法对你的消费者来说有点误导api,因为他们不会假设您的任务中没有额外的线程。 What you should do is expose your API synchronously, and let the user offload it using Task.Run himself, controlling the flow of execution. 你应该做的是同步公开你的API,并让用户使用Task.Run自己卸载它,控制执行流程。

async methods are primarily used for I/O Bound operations, since these naturally don't need any threads to be consumed while the IO operation is executing, and that is why we see them alot in classes responsible for doing IO operations, such as hard drive calls, network calls, etc. async方法主要用于I / O绑定操作,因为这些操作执行时自然不需要消耗任何线程,这就是为什么我们在负责执行IO操作的类中看到它们很多,比如硬操作开车电话,网络电话等

I suggest reading the Parallel PFX teams article Should I expose asynchronous wrappers for synchronous methods? 我建议阅读Parallel PFX团队文章我应该为同步方法公开异步包装器吗? which talks exactly about what you're trying to do and why it isn't recommended. 它准确地说明了你想要做什么以及为什么不推荐它。

TL;DR: TL; DR:

Task.Run() is what you want, but be careful about hiding it in your library. Task.Run()是你想要的,但要小心将它隐藏在你的库中。

I could be wrong, but you might be looking for guidance on getting CPU-Bound code to run asynchronously [by Stephen Cleary]. 我可能是错的,但你可能正在寻找关于让CPU-Bound代码异步运行的指导[由Stephen Cleary]。 I've had trouble finding this too, and I think the reason it's so difficult is that's kinda not what you're supposed to do for a library - kinda... 我也很难找到这个,而且我认为它之所以如此困难,是因为它不是你应该为图书馆做的 - 有点......

Article 文章

The linked article is a good read (5-15 minutes, depending) that goes into a decent amount of detail about the hows and whys of using Task.Run() as part of an API vs using it to not block a UI thread - and distinguishes between two "types" of long-running process that people like to run asynchronously: 链接的文章是一个很好的阅读(5-15分钟,取决于),有关使用Task.Run()作为API的一部分和使用它来阻止UI线程的方法和原因的大量详细信息 -并区分人们喜欢异步运行的两个长期运行过程的“类型”:

  • CPU-bound process (one that is crunching / actually working and needs a separate thread to do its work) 受CPU限制的进程 (一个正在处理/实际工作并需要一个单独的线程来完成其工作的进程)
  • Truly asynchronous operation (sometimes called IO-bound - one that is doing a few things here and there with a bunch of waiting time in between actions and would be better off not hogging a thread while it's sitting there doing nothing). 真正的异步操作 (有时称为IO绑定 - 在这里和那里做一些事情,在操作之间有一堆等待时间,并且最好不要在它坐在那里什么都不做时占用线程)。

The article touches on the use of API functions in various contexts, and explains whether the associated architectures "prefer" sync or async methods, and how an API with sync and async method signatures "looks" to a developer. 本文讨论了在各种上下文中使用API​​函数,并解释了相关体系结构“更喜欢”同步或异步方法,以及具有同步和异步方法签名的API如何“看起来”给开发人员。

Answer 回答

The last section " OK, enough about the wrong solutions? How do we fix this the right way??? " goes into what I think you're asking about, ending with this: 最后一节“ 好的,关于错误的解决方案?我们如何以正确的方式解决这个问题? ”进入我认为你所问的问题,结束于此:

Conclusion: do not use Task.Run in the implementation of the method; 结论:在方法的实现中不要使用Task.Run; instead, use Task.Run to call the method. 相反,使用Task.Run来调用该方法。

Basically, Task.Run() 'hogs' a thread, and is thus the thing to use for CPU-bound work, but it comes down to where it's used. 基本上, Task.Run() “猪”一个线程,因此使用了CPU限制的工作的事情,但它归结为它使用的地方 When you're trying to do something that requires a lot of work and you don't want to block the UI Thread, use Task.Run() to run the hard-work function directly (that is, in the event handler or your UI based code): 当你试图做一些事情,需要大量的工作,你不想阻止用户界面线程,使用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());
}

But... don't hide your Task.Run() in an API function suffixed -Async if it is a CPU-bound function, as basically every -Async function is truly asynchronous, and not CPU-bound. 但是......如果它是一个CPU绑定函数, 请不要在API函数后缀-Async隐藏Task.Run() ,因为基本上每个-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());
  }
}

In other words, don't call a CPU-bound function -Async , because users will assume it is IO-bound - just call it asynchronously using Task.Run() , and let other users do the same when they feel it's appropriate. 换句话说,不要调用CPU绑定函数-Async ,因为用户会认为它是IO绑定的 - 只需使用Task.Run()异步调用它,并让其他用户在认为合适的时候也这样做。 Alternately, name it something else that makes sense to you (maybe BeginAsyncIndependentWork() or StartIndependentWorkTask() ). 或者,将其命名为对您有意义的其他内容(可能是BeginAsyncIndependentWork()StartIndependentWorkTask() )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM