我很确定这里有一个重复的,但我找不到它。
很简单,这样做更好吗:
await MyMethod();
跟:
public async Task MyMethod()
{
Thread.Sleep(10000); // Simulates long running operation
}
或涉及 Task.Run 的东西?
public async Task MyMethod()
{
await Task.Run(() => Thread.Sleep(10000)); // Simulates long running operation
}
实际长时间运行的操作是不支持异步的阻止调用。
第一个示例显然有视觉工作室抱怨它将同步运行。使该方法异步的原因是因为我希望人们能够轻松await
,这样他们就不会占用他们的线程。
在第一个示例中使用await
是没有用的,因为Task
只会在方法完成后返回,此时Task
也已完成。
我也不会使用第二个示例;不要假设调用者希望该方法在线程池上运行;允许他们自己决定:
public void MyMethod()
{
Thread.Sleep(10000); // Simulates long running operation
}
访客:
await Task.Run(MyMethod);
我认为这里要指出的关键点是,如果一个方法不是真正的异步,它不应该声称是异步的。
在某个位置,线程被阻止,无论是当前 (UI( 线程还是线程池上的另一个线程。
由于在线程之间切换,Task.Run
带来了开销,这可能超过好处,具体取决于使用者。
在 ASP.NET 环境中,同步运行该方法可能更可取;HTTP 响应只能在操作完成后发送,那么为什么要引入开销呢?
在桌面环境中,保持 UI 线程响应是关键,因此创建的开销是值得的。
Tl;博士
是否使用async
取决于此代码是否阻塞。如果它正在阻塞并且您无法使其解锁,则不要使用async
,您只需要处理阻止代码。如果您可以使用async
使其非阻塞,则这样做。添加线程(使用Task.Run()
而不是Thread
(取决于此代码正在做什么,是IO还是CPU绑定?
如果此代码阻塞并且您无法更改它,那么正确的实现将是:
public void MyMethod()
{
// Long running operation
}
事实上,如果你的代码是阻塞的并且不包含await
调用,这正是预编译器将把你的方法变成什么(async
或不
通常,如果您使用async
则始终使用Task
类(及其方法(,因为这会在底层线程上添加一层抽象并启用async
模式。TBH几乎一直使用Task
,自 TPL 以来,您很少需要直接与线程交互。在代码审查中,我基本上拒绝任何人现在使用Thread
,并告诉他们改用Task
。
在async
方法中模拟延迟的正确方法是使用Task.Delay()
,因此:
public async Task MyMethod()
{
await Task.Delay(10000).ConfigureAwait(false); // Simulates long running operation
}
这会在发生延迟时将线程释放回线程池。您当前的方法会阻塞主线程,因此优化程度低于预期。ConfigureAwait(false)
只是围绕上下文切换添加了更多优化(这取决于您在此处的上下文,因此请阅读该链接(。
这种方法是两全其美的:
public async Task MyMethod()
{
await Task.Run(() => Thread.Sleep(10000)); // Simulates long running operation
}
这现在释放了主线程(我猜很好(,但它所做的只是创建一个新线程(具有上下文切换的所有开销(,然后阻塞。所以你仍然使用一个线程,这个线程仍然被阻塞,但现在你只是在混合中添加了大量的额外上下文切换以减慢一切。所以这是非常低效的。
使该方法异步的原因是因为我希望人们 能够轻松等待,这样他们就不会束缚他们的线程。
如果你绝对希望人们能够等待这个,并且你有完全同步的代码,你不能改变它以使其返回一个可等待的,它肯定会阻塞调用线程,而你不希望这样,那么你唯一的选择是使用Task.Run()
(或它的衍生物(。
请注意,这被许多人视为反模式。 正确的方法是更改您的要求,以便
不需要等待。public async Task MyMethod()
{
await Task.Run(() => LongRunningBlockingSyncMethod());
}