简体   繁体   English

Task.Run() 中的异步/等待操作

[英]Async/Await action within Task.Run()

Task.Run(()=>{}) puts the action delegate into the queue and returns the task . Task.Run(()=>{})将动作委托放入队列并返回 task 。 Is there any benefit of having async/await within the Task.Run() ?Task.Run()中使用 async/await 有什么好处吗? I understand that Task.Run() is required since if we want to use await directly, then the calling method will need to be made Async and will affect the calling places.我知道Task.Run()是必需的,因为如果我们想直接使用 await,那么调用方法将需要设为异步并会影响调用位置。

Here is the sample code which has async await within Task.Run() .这是在Task.Run()具有异步等待的示例代码。 The full sample is provided here: Create pre-computed tasks .此处提供了完整示例: 创建预先计算的任务

Task.Run(async () => { await new WebClient().DownloadStringTaskAsync("");});

Alternatively this could have been done:或者,这可以完成:

Task.Run(() => new WebClient().DownloadStringTaskAsync("").Result;);

Since both, Task.Run() and Await will queue the work and will be picked by the thread pool, could the async/await within the Task.Run() be a bit redundant?由于Task.Run()Task.Run()都会将工作排队并由线程池选择, Task.Run()的 async/await 是否有点多余?

Is there any benefit of having async/await within the Task.Run() ?在 Task.Run() 中使用 async/await 有什么好处吗?

Yes.是的。 Task.Run runs some action on a thread-pool thread. Task.Run在线程池线程上运行一些操作。 If such action does some IO work and asynchronously waits for the IO operation to complete via await , then this thread-pool thread can be used by the system for other work while the IO operation is still running.如果这样的动作做了一些 IO 工作并通过await异步等待 IO 操作完成,那么这个线程池线程可以在 IO 操作仍在运行时被系统用于其他工作。

Example:例子:

Task.Run( async () =>
{
    DoSomeCPUIntensiveWork();

    // While asynchronously waiting for this to complete, 
    // the thread is given back to the thread-pool
    var io_result = await DoSomeIOOperation(); 

    DoSomeOtherCPUIntensiveWork(io_result);
});

Is there any benefit of having async/await within the Task.Run()在 Task.Run() 中使用 async/await 有什么好处吗

You also could ask the opposite question: Why would you wrap an async/await code in Task.Run?!您也可以问相反的问题:为什么要在 Task.Run 中包装 async/await 代码?!

An async method returns to the caller as soon as the first await is hit (that operates on a non-completed task).一旦第一个等待被命中(对未完成的任务进行操作),异步方法就会返回给调用者。 So if that first execution "streak" of an async method takes a long time Task.Run will alter behavior: It will cause the method to immediately return and execute that first "streak" on the thread-pool.因此,如果异步方法的第一次执行“连续”需要很长时间, Task.Run将改变行为:它将导致该方法立即返回并在线程池上执行第一个“连续”。

This is useful in UI scenarios because that way you can make 100% sure that you are not blocking the UI.这在 UI 方案中很有用,因为这样您可以 100% 确保您没有阻止 UI。 Example: HttpWebRequest does DNS resolution synchronously even when you use one of the async methods (this is basically a library bug/design error).示例:即使您使用其中一种异步方法(这基本上是库错误/设计错误), HttpWebRequest也会同步进行 DNS 解析。 This can pause the UI thread.这可以暂停 UI 线程。 So you can use Task.Run to be 100% sure that the UI is never blocked for longer than a few microseconds.因此,您可以使用 Task.Run 来 100% 确保 UI 不会被阻塞超过几微秒。

So back to the original question: Why await inside a Task.Run body?所以回到最初的问题:为什么在 Task.Run 主体内等待? For the same reason you normally await: To unblock the thread.出于与您通常等待相同的原因:解除线程阻塞。

In the example that you linked the main thread is being blocked until the asynchronous operation is done.在您链接的示例中,主线程被阻塞,直到异步操作完成。 It's being blocked by calling Wait() (which by the way is generally a bad idea).它被调用Wait()阻止Wait()顺便说一句,这通常是一个坏主意)。

Let's have a look at the return from the DownloadStringAsync in the linked sample:让我们看看链接示例中DownloadStringAsync的返回:

return Task.Run(async () =>
{
    content = await new WebClient().DownloadStringTaskAsync(address);
    cachedDownloads.TryAdd(address, content);
    return content;
});

Why would you wrap this in a Task ?为什么要将其包装在Task Think about your options for a second.考虑一下您的选择。 If you don't want to wrap this in a Task , how would you make sure the method returns a Task<string> and still have it work?如果您不想将其包装在Task ,您将如何确保该方法返回Task<string>并且仍然有效? You'd mark the method as async of course!您当然async方法标记为async However, if you mark your method as async and you call Wait on it, you'll most likely end up with a deadlock, since the main thread is waiting for the work to finish, and your blocking the main thread so it can't let you know it's done.但是,如果您将方法标记为async并对其调用Wait ,则很可能会以死锁告终,因为主线程正在等待工作完成,而您阻塞了主线程,因此它不能让你知道它已经完成。

When marking a method as async , the state machine will run on the calling thread, in your example however, the state machine runs on a separate thread, meaning there is little to no work being done on the main thread.将方法标记为async ,状态机将在调用线程上运行,但是在您的示例中,状态机在单独的线程上运行,这意味着在主线程上几乎没有工作。

Calling Async from Non-Async Methods从非异步方法调用异步

We do some stuff like that when we are trying to call an async method inside of a non-async method.当我们尝试在非异步方法中调用异步方法时,我们会做一些类似的事情。 Especially if the async method is a known quantity.特别是如果异步方法是已知数量。 We use more of a TaskFactory though ... fits a pattern, makes it easier to debug, makes sure everyone takes the same approach (and -- gives us one throat to choke if async-->sync starts acting buggy).尽管我们使用了更多的 TaskFactory ......适合一种模式,使其更容易调试,确保每个人都采用相同的方法(并且 -- 如果 async-->sync 开始出现问题,我们就会窒息)。

So, Your Example所以,你的例子

Imagine, in your example, that you have a non-async function.想象一下,在您的示例中,您有一个非异步函数。 And, within that function, you need to call await webClient.DoSomethingAsync() .并且,在该函数中,您需要调用await webClient.DoSomethingAsync() You can't call await inside of a function that's not async -- the compiler won't let you.您不能在非异步函数内调用 await - 编译器不会让您这样做。

Option 1: Zombie Infestation选项 1:僵尸感染

Your first option is to crawl all the way back up your call stack, marking every da*n method along the way as async and adding awaits everywhere.您的第一个选择是一路爬行备份您的调用堆栈,沿途将每个 da*n 方法标记为异步并在各处添加等待。 Everywhere.到处。 Like, everywhere.就像,无处不在。 Then -- since all those methods are now async, you need to make the methods that reference them all async.然后——由于所有这些方法现在都是异步的,您需要使引用它们的方法全部异步。

This, btw, is probably the approach many of the SO enthusiasts are going to advocate.顺便说一句,这可能是许多 SO 爱好者将要提倡的方法。 Because, "blocking a thread is a bad idea."因为,“阻塞线程是个坏主意。”

So.所以。 Yeah.是的。 This "let async be async" approach means your little library routine to get a json object just reached out and touched 80% of the code.这种“让异步成为异步”的方法意味着您用来获取 json 对象的小库例程刚刚触及并触及了 80% 的代码。 Who's going to call the CTO and let him know?谁会打电话给 CTO 让他知道?

Option 2: Just Go with the One Zombie选项 2:只用一个僵尸

OR, you can encapsulate your async inside of some function like yours...或者,你可以将你的异步封装在一些像你这样的函数中......

return Task.Run(async () => {
     content = await new WebClient().DoSomethingAsync();
     cachedDownloads.TryAdd(address, content);
     return content;
});

Presto... the zombie infestation has been contained to a single section of code. Presto...僵尸感染已包含在一段代码中。 I'll leave it to the bit-mechanics to argue over how/why that gets executed at the CPU-level.我将把它留给位机制来争论如何/为什么在 CPU 级别执行。 I don't really care.我真的不在乎。 I care that nobody has to explain to the CTO why the entire library should now be 100% async (or something like that).我关心的是没有人需要向 CTO 解释为什么整个库现在应该是 100% 异步的(或类似的东西)。

Confirmed, wrapping await with Task.Run spawn 2 threads instead of one.确认,用Task.Run包装await产生 2 个线程而不是一个。

Task.Run(async () => { //thread 1
 await new WebClient().DownloadStringTaskAsync(""); //thread 2
});

Say you have two calls wrapped like this, it will spawn 2 x 2 = 4 threads.假设您有两个这样包装的调用,它将产生 2 x 2 = 4 个线程。

It would be better to just call these with simple await instead.最好用简单的await来调用它们。 For example:例如:

Task<byte[]> t1 = new WebClient().DownloadStringTaskAsync("");
Task<byte[]> t2 = new WebClient().DownloadStringTaskAsync("");
byte[] t1Result = await t1;
byte[] t2Result = await t2;

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

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