Does await really does Task.Run()
await的背后并不使用Task.Run。
await不仅仅适用于Task.Run。它用于在某个操作完成后运行代码。这个操作可以是使用Task.Run创建的计算任务,也可以是数据检索,比如使用ReadLineAsync。Task是用来模拟所有这些操作的类。
Task.Run用于将工作排队到线程池中运行。这在编写CPU密集型代码时是合适的,但在处理IO密集型操作时不应使用Task.Run。在这种情况下,您的异步代码(可能使用async/await)应该使用异步IO方法,如WriteLineAsync和前面提到的ReadLineAsync。
在工作线程上运行阻塞代码使用Task.Run是一个反模式。即使它解除了GUI线程的阻塞,也不应该这样做。
有时,人们在异步方法中使用await,但有时也会看到人们在没有异步方法的情况下使用Task.Run。哪种方式才是正确的?
确实可以使用Task.Run创建一个任务,然后让它自行运行,而不使用await或ContinueWith。这些被称为“fire and forget”任务。它们很少是一个好主意。与使用await不同,该任务抛出的任何异常都将丢失。
这个问题在Stephen Cleary的StackOverflow回答中有解释。
在使用async和await时,我们经常会遇到一个问题:是否真的需要在Task.Run()方法上使用await操作符。这个问题的出现是因为async操作符只是在方法中启用了await操作符的使用。而await操作符可以用于任何可等待的对象。在.NET中,Task和Task<T>就是一些可等待的对象的例子。
Task.Run()方法返回Task对象,Task.Run<T>()方法返回Task<T>对象,正如我所说的,它们都是可等待的对象,所以你可以在它们上面使用await操作符。
如果你愿意,你甚至可以自己编写一个可等待的类型:你只需要确保满足被认为是可等待的要求。一个可等待的类型需要提供GetAwaiter的实现就可以了。下面是一个可等待的示例:
public struct SimpleInt32Awaitable { public SimpleInt32Awaiter GetAwaiter() { return new SimpleInt32Awaiter(); } } public struct SimpleInt32Awaiter { public bool IsCompleted { get { return true; } } public void OnCompleted(Action continuation) { } public int GetResult() { return 5; } }
现在你可以这样做:
// 在其他某个类中 static async void SimpleWaitAsync() { SimpleInt32Awaitable awaitable = new SimpleInt32Awaitable(); int result = await awaitable; }
所以,回到最初的问题:是否真的需要在Task.Run()方法上使用await操作符?答案是取决于具体情况。如果你想在异步操作中执行一些计算密集型的工作,并且不希望阻塞主线程,那么在Task.Run()上使用await是有意义的。但是,如果你只是想在异步操作中执行一些简单的工作,而不需要等待它完成,那么在Task.Run()上使用await是没有必要的。