等待同步的异步方法是否可以接受?

时间:2020-01-31 10:13:00

标签: c# async-await

我很确定这里有一个重复项,但找不到。

很简单,这样做是否更好:

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
}

实际上长时间运行的操作是不支持异步的阻塞调用。

第一个示例显然使Visual Studio抱怨它将同步运行。使该方法异步的原因是,我希望人们能够轻松await,以免他们占用线程。

3 个答案:

答案 0 :(得分:5)

在第一个示例中使用await是没有用的,因为Task仅在方法完成后返回,此时Task也将完成。

我也不会使用第二个示例;不要假设调用者希望该方法在线程池上运行;让他们自己决定:

public void MyMethod()
{
   Thread.Sleep(10000); // Simulates long running operation
}

来电者:

await Task.Run(MyMethod);

我认为这里要说明的关键是,如果方法不是真正的异步方法,则不应声称是这样。

在该行的某处,一个线程被阻塞,无论该线程是当前(UI)线程,还是线程池上的另一个线程。

Task.Run由于线程之间的切换而带来开销,具体取决于使用方。

在ASP.NET环境中,最好同步运行该方法。 HTTP响应只能在操作完成后发送,所以为什么要增加开销?

在桌面环境中,保持UI线程的响应速度很关键,因此创建的开销值得。

答案 1 :(得分:3)

Tl; Dr

是否使用async取决于此代码是否被阻止。如果它正在阻塞并且您无法使其解除阻塞,那么就不要使用async,而只需要处理阻塞代码即可。如果您可以使其变为非阻塞状态,请使用async。 (使用Task.Run()而不是Thread添加线程取决于此代码在做什么,是IO or CPU bound吗?

如果此代码被阻止,并且您无法更改,则正确的实现将是:

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)仅添加了更多optimisation around context switching(这取决于您的上下文,因此请阅读该链接)。


这种方法是两全其美的方法:

public async Task MyMethod()
{
   await Task.Run(() => Thread.Sleep(10000)); // Simulates long running operation
}

这现在释放了主线程(我想很好),但是它所做的就是创建一个新线程(包含所有上下文切换的开销),然后将其阻塞。因此,您仍在使用一个线程,该线程仍处于阻塞状态,但现在您刚刚将大量额外的上下文切换添加到混合中,以减慢所有操作。所以这效率很低。

答案 2 :(得分:0)

使方法异步的原因是因为我希望人们成为 能够轻松等待,以免他们占用线程。

如果您绝对希望人们能够等待它,并且您拥有完全同步的代码,则无法对其进行更改以使其返回可等待的代码,并且肯定会阻塞调用线程,并且您不希望这样做,那么您唯一的选择是使用Task.Run()(或其衍生版本)。

请注意,这被许多人视为反模式。正确的方法是更改​​您的要求,以使其不必等待。

public async Task MyMethod()
{
    await Task.Run(() => LongRunningBlockingSyncMethod());
}