【发布时间】:2015-06-29 22:05:42
【问题描述】:
考虑这段代码:
public async Task DoStuffAsync() {
await WhateverAsync(); // Stuff that might take a while
}
// Elsewhere in my project...
public async Task MyMethodAsync() {
await DoStuffAsync();
}
public void MyMethod() {
DoStuffAsync(); // <-- I want this to block until Whatever() is complete.
}
我想提供两种功能相同的方法:异步运行的异步方法和阻塞的非异步方法,这两种方法中的任何一种都可以根据我调用的代码是否被调用也是异步的。
我不想到处重复加载代码并为每个方法创建两个版本,浪费时间和精力并降低可维护性。
我猜我可以执行以下操作之一:
// Call it just like any other method...
// the return type would be Task though which isn't right.
DoStuffAsync();
// Wrap it in a Task.Run(...) call, this seems ok, but
// but is doesn't block, and Task.Run(...) itself returns an
// awaitable Task... back to square one.
Task.Run(() => DoStuffAsync());
// Looks like it might work, but what if I want to return a value?
DoStuffAsync().Wait();
这样做的正确方法是什么?
更新
感谢您的回答。一般的建议似乎只是提供一个 Async 方法并让消费者决定,而不是创建一个包装器方法。
但是,让我试着解释一下我的现实问题......
我有一个 UnitOfWork 类,它包装了 EF6 DbContext 的 SaveChanges() 和 SaveChangesAsync() 方法。我还有List<MailMessage> 的电子邮件,仅 需要在 SaveChanges() 成功时发送。所以我的代码如下所示:
private readonly IDbContext _ctx;
private readonly IEmailService _email;
private readonly List<MailMessage> _emailQueue;
// ....other stuff
public void Save() {
try {
_ctx.SaveChanges();
foreach (var email in _emailQueue) _email.SendAsync(email).Wait();
} catch {
throw;
}
}
public async Task SaveAsync() {
try {
await _ctx.SaveChangesAsync();
foreach (var email in _emailQueue) await _email.SendAsync(email);
} catch {
throw;
}
}
所以您可以看到,因为 EF6 DbContext 提供异步和非异步版本,所以我也必须这样做...根据目前的答案,我已将 .Wait() 添加到我的 @987654330 @ 方法,通过 SMTP 异步发送电子邮件。
所以...一切都很好,除了死锁问题...我如何避免死锁?
【问题讨论】:
-
或
DoStuffAsync().Result; -
如果代码本质上是异步的,只需提供异步方法并让消费者决定在哪里或如何阻止它。不要提供只做消费者可以自己做的事情的同步包装方法。
-
事实上,Stephen Toub 就这个主题写了一篇很棒的blog post。
-
@Damien_The_Unbeliever:好点...但我仍然需要知道如何去做,因为我也在编写消费者代码 :)
-
我认为“正确”的方法是使消费者也异步,慢慢冒泡,直到您从 UI 到达事件侦听器。我想它并没有回答你关于如何阻止同步代码的问题。
标签: c# asynchronous async-await task-parallel-library