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



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

很简单,这样做更好吗:

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

最新更新