在这个父方法中,我如何等待一个没有异步修饰符的异步方法?

25

我有一个想要等待的方法,但我不想引起连锁反应,认为任何东西都能调用这个调用方法并等待它。例如,我有这个方法:

public bool Save(string data)
{
   int rowsAffected = await UpdateDataAsync(data);
   return rowsAffected > 0;
}

我正在打电话:

public Task<int> UpdateDataAsync()
{
  return Task.Run(() =>
  {
    return Data.Update(); //return an integer of rowsAffected
  }
}
这样不起作用,因为我必须在 Save() 方法签名中加入 "async" ,然后我不能返回 bool ,而是必须将其改为 Task<bool>,但我不想让任何人等待 Save() 方法。有没有一种方法可以暂停代码执行,例如使用 await 或以某种方式等待此代码,而无需使用 async 修饰符?

3
如果你给我访问一个会阻塞我的线程的“保存”方法,强制我将调用包装在“任务”中,那么我会非常恼火,而这正是你本可以一开始就返回给我的。只是说说而已。 - Kent Boogaart
2个回答

15
如何在不使用async修饰符的情况下等待异步方法? 这有点像问“如何使用C#编写应用程序,但不依赖任何.NET运行时?” 简短回答:不要这样做。 实际上,你在这里所做的是将自然同步方法(Update)通过在线程池线程上运行(UpdateDataAsync),使其显示为异步方法,然后你希望阻塞它以使异步方法显示为同步方法(Save)。这是非常危险的。 我建议你仔细研究Stephen Toub的著名博客文章 “是否应该为我的同步方法公开异步包装器”“是否应该为我的异步方法公开同步包装器”。对于这两个问题的答案都是“否”,尽管Stephen Toub解释了几种选项可供选择。 “真正需要”的应该保留给应用程序级别。我假设这些方法(Update、UpdateDataAsync和Save)位于应用程序的不同层中(例如数据/数据服务/视图模型)。数据/数据服务层不应进行同步/异步转换。视图模型(应用程序特定)级别是唯一有理由进行这种转换的层次,但只有在最后一种情况下才可以这样做。

如果我正在编写一个测试用例,在其中使用异步驱动程序将内容放入数据库中,然后立即将其读取出来,该怎么办呢?只是说,同步编程也有合法的用例。 - Hypershadsy
@Hypershadsy:为什么你不能异步地完成这个任务呢? - Stephen Cleary
1
你可以编写异步测试用例吗? - Hypershadsy
@Hypershadsy:是的。所有主要的单元测试框架(xUnit、MSTest、NUnit)都支持async Task单元测试方法自2012年以来 - Stephen Cleary

12

编辑:在添加Task.Run之前,此答案已经存在。有了这个额外的上下文信息,最好描述情况为“不要那样做”。


你可以访问 .Result 或使用 .Wait(),但你需要先知道任务是如何实现的。特别是,你需要知道它是否使用同步上下文(sync-context)。这很重要的原因是,如果它这样做了,可能会立即死锁,因为某些同步上下文需要调用上下文完全退出(例如,MVC的同步上下文需要离开控制器的操作方法)。

为了防止这种情况发生是很困难的,但你应该在使用.Wait()时总是明确指定超时时间 - 以防万一。


谢谢,我正在使用Task.Run - 我更新了我的问题以展示第二种方法。 - Neal
@Neal,根据编辑,我想说只要“不要那么做”——你这样做没有任何理由添加一个线程。你最好在那个将要等待的线程上处理这项工作。 - Marc Gravell
2
@Neal:Marc 绝对是正确的;你在这里做的本质上是雇佣某人代表你等待,然后等待他们完成代表你等待的工作。在现实生活中,你不会雇佣某人来做这件事,所以在这里也不要这样做。如果您需要同步等待任务完成,则请这样做;如果您想在等待时继续工作,请使用await - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接