C# 从同步代码调用异步方法并阻止直到任务完成的正确方法是什么?
考虑以下代码:C# 从同步代码调用异步方法并阻止直到任务完成的正确方法是什么?,c#,asynchronous,async-await,task-parallel-library,C#,Asynchronous,Async Await,Task Parallel Library,考虑以下代码: 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(); // &
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.
}
正确的方法是什么
更新
谢谢你的回答。一般的建议似乎只是提供一个异步方法并让使用者决定,而不是创建一个包装器方法
然而,让我试着解释一下我的现实问题
我有一个UnitOfWork
类,它包装了EF6DbContext
的SaveChanges()和savechangesync()方法。我还有一个列表
,只有在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;
}
}
public async Task SaveAsync() {
await _ctx.SaveChangesAsync();
await SendEmailsAndIgnoreErrors(_email, _emailQueue);
}
private只读IDbContext\u ctx;
私人只读IEmailService\u电子邮件;
私有只读列表\u emailQueue;
//……其他东西
公共作废保存(){
试一试{
_ctx.SaveChanges();
foreach(电子邮件队列中的var email)\u email.SendAsync(email.Wait();
}抓住{
投掷;
}
}
公共异步任务SaveAsync(){
试一试{
wait_ctx.saveChangesSync();
foreach(电子邮件队列中的var电子邮件)等待电子邮件。SendAsync(电子邮件);
}抓住{
投掷;
}
}
所以您可以看到,因为EF6DbContext
提供了异步和非异步版本,所以我也不得不。。。根据到目前为止的答案,我已将.Wait()
添加到我的SendAsync(email)
方法中,该方法通过SMTP异步发送电子邮件
所以。。。很好,除了死锁问题。。。如何避免死锁?如何执行阻塞等待。
// Looks like it might work, but what if I want to return a value?
DoStuffAsync().Wait();
确实如此。关于
如果我想返回一个值呢
假设您还有一个异步方法,它返回一个任务:
关于您的意图:
我想提供两个具有相同功能的方法:异步
异步运行的方法和阻塞的非异步方法,
这两个方法中的任何一个都可以调用,这取决于
我从中调用的代码也是异步的
创建只执行MyWhatEverAsync.Wait()
的MyWhatEverAsync.Wait()
重载不是推荐的模式,因为它可能会对调用该方法的客户端产生意外的副作用,例如。调用方法的同步版本的客户端可能不希望其线程因调用方法而被阻塞,并且无法自行决定这是否可接受(例如,它无法指定ConfigureAwait
)。正如@Default在评论中提到的,这可能导致
相反的情况通常也是如此:例如,有一个MyWhatEverAsync()
只执行返回任务。运行(()=>mywhater())代码>如果客户需要,他们可以自己完成,并包括取消令牌等,以便控制任务
最好在接口中显式显示,并且只提供按签名所述执行的方法
针对您的现实问题(问题编辑后):
我有一个UnitOfWork
类,它包装了SaveChanges()
和
savechangessync()
EF6的方法DbContext
。我也有一个
仅当
SaveChanges()
成功
和(来自评论)
发送电子邮件的成功并不重要
您当前对Save
和SaveAsync
方法的UnitOfWork
设计不正确,因为它违反了原子性承诺
在“SaveChanges”成功,但发送其中一封电子邮件时发生异常的场景中。调用保存
变体之一的客户端代码将观察到该异常,将(并且应该)假定保存更改失败,并可以重试应用更改
如果客户端使用两阶段提交在事务上下文中包装此操作,“SaveChanges”将回滚,并且当客户端重试此操作时,它将尝试再次发送电子邮件,这可能会导致多个收件人多次接收电子邮件,尽管他们收到电子邮件时所做的更改未成功提交(如果发送电子邮件多次失败,则可能永远不会成功提交)
所以你需要改变一些东西
如果你真的不关心发送电子邮件的成功,我建议你添加以下方法
private Task SendEmailsAndIgnoreErrors(IEmailService emailService, List<MailMessage> emailQueue) {
return Task.WhenAll(emailQueue.Select(async (m) => {
try {
await emailService.SendAsync(m).ConfigureAwait(false);
} catch {
// You may want to log the failure though,
// or do something if they all/continuously fail.
}
}));
}
我个人会省略同步变量,让客户决定如何等待任务,但如果您认为确实需要它,您可以这样做:
public void Save() {
_ctx.SaveChanges();
SendEmailsAndIgnoreErrors(_email, _emailQueue).Wait().ConfigureAwait(false);
}
正确的方法是什么
正确的方法是做你不想做的事情,公开两种不同的方法,一种是完全同步的,另一种是完全异步的
在处理异步代码时,公开和隐藏反模式
如果这些方法不是天生异步的,我将只公开一个同步版本,并让调用程序决定是否使用任务来包装它。自己运行
如果该方法是自然异步的,我将只公开异步方法。如果同时有同步和异步版本,请单独公开它们
异步方法“一路走”,当您使用它们时,它们慢慢地在调用堆栈上下冒泡。接受这一事实,您将避免一路上的许多陷阱。有一个Github项目:
项目主页中包含的示例:
public async Task<string> AsyncString()
{
await Task.Delay(1000);
return "TestAsync";
}
public void Test()
{
string string1 = "";
string string2 = "";
using (var A = AsyncHelper.Wait)
{
A.Run(AsyncString(), res => string1 = res);
A.Run(AsyncString(), res => string2 = res);
}
}
公共异步任务AsyncString()
{
等待任务。延迟(1000);
返回“TestAsync”;
}
公开无效测试()
{
字符串string1=“”;
字符串string2=“”;
使用(var A=asynchHelper.Wait)
{
A.Run(AsyncString(),res=>string1=res);
A.Run(AsyncString(),res=>string2=res);
}
}
我在几个项目中使用了它
public async Task SaveAsync() {
await _ctx.SaveChangesAsync();
await SendEmailsAndIgnoreErrors(_email, _emailQueue);
}
public void Save() {
_ctx.SaveChanges();
SendEmailsAndIgnoreErrors(_email, _emailQueue).Wait().ConfigureAwait(false);
}
public async Task<string> AsyncString()
{
await Task.Delay(1000);
return "TestAsync";
}
public void Test()
{
string string1 = "";
string string2 = "";
using (var A = AsyncHelper.Wait)
{
A.Run(AsyncString(), res => string1 = res);
A.Run(AsyncString(), res => string2 = res);
}
}