【问题标题】:Is making long-running calls async this simple?让长时间运行的调用异步这么简单吗?
【发布时间】:2016-04-26 18:42:06
【问题描述】:

我只是粗略地使用以下模式将大量数据访问方法转换为异步,它看起来太简单以至于无法满足以后的迭代。它有多安全,缺少什么,我应该怎么做?

提供长时间运行调用的服务:

private class UserService
{
    public IdentityUser GetById(int id)
    {
        ...
    }
}

private UserService _userService = new UserService();

原同步方式:

public IdentityUser GetById(int id)
{
    return _userService.GetById(id);
}

我奇妙的新异步方法:

public async Task<IdentityUser> GetByIdAsync(int id)
{
    await Task.Run(() => _userService.GetById(id));
}

【问题讨论】:

  • 长时间运行的查询有很多您需要处理的可能问题。为什么不直接安装 SignalR nuget 并为您修复它们 - 以及 websocket 支持!
  • @MattiasÅslund:不同的人对“长跑”有不同的定义。我有一种感觉 GetById() 不是那种值得使用 SignalR 的“长期运行”。
  • 它是您作为 WCF 服务还是 Web API 使用的东西?如果是这样,你只会让服务器端的事情变得更糟。
  • 如果您正在考虑同步方法的异步包装器,请参阅this

标签: c# asynchronous async-await c#-5.0


【解决方案1】:

你不应该做这样的“假异步”方法:

public async Task<IdentityUser> GetByIdAsync(int id)
{
    await Task.Run(() => _userService.GetById(id));
}

我称其为“假异步”的原因是因为该操作本质上没有任何异步。在这种情况下,您应该只有同步方法。如果调用者想通过使用Task.Run 使其异步,他可以这样做。

什么时候本质上是异步的?例如,当您向 Web 服务或数据库发出请求时,在发送请求和接收响应之间存在等待期——请求本质上是异步操作。为避免阻塞调用线程,请使用 async-await。

【讨论】:

  • 我必须完全按照public async Task&lt;IdentityUser&gt; GetByIdAsync(int id) 来实现该方法,如果我不将其内部操作设为假异步,它将无法编译,而UserService 不提供任何异步方法。
  • @ProfK,然后将其实现为返回 Task.FromResult 并且没有 async 关键字(无论如何这不是编译方法签名的一部分)。
  • @Noseratio 我很喜欢Task.FromResult,并且在很多地方都使用过它,我不得不返回Task 甚至await 一个。我想我可能会谨慎行事,让我所有的“假货”都使用它。
  • @ProfK,你也可以作弊:如果你真的喜欢async exception propagation semantic,请保留async 并在其中执行await Task.FromResult(0) 以使编译器满意。这仍然比使用 Task.Run 伪造异步要好。
【解决方案2】:

从技术上讲,这是可行的,但它通过创建一个新线程来执行 同步 操作来工作,该操作本身就是包装和阻塞固有的 异步 操作。这意味着您并没有从一开始就获得async 的一些最大好处。

正确的方法是一路异步。而现在你可能有这样的东西:

private class UserService
{
    public IdentityUser GetById(int id)
    {
        return mContext.Users.Single(u => u.Id == id);
    }
}

...您现在应该创建一个异步版本:

private class UserService
{
    public async Task<IdentityUser> GetByIdAsync(int id)
    {
        return await mContext.Users.SingleAsync(u => u.Id == id);
    }
}

用法:

public async Task<IdentityUser> GetByIdAsync(int id)
{
    return await _userService.GetByIdAsync(id);
}

当然,假设您的底层框架支持像 SingleAsync() 这样的异步方法来进行固有的异步操作,这将允许系统在您等待数据库操作完成时释放当前线程。该线程可以在其他地方重用,当操作完成后,您可以使用当时恰好可用的任何线程。

这可能也值得阅读和采用these Best Practices。例如,您可能希望在不访问会话和请求等上下文信息的任何地方使用.ConfigureAwait(false)

当然,这个答案假设GetById 本质上是异步的:您正在从硬盘驱动器或网络位置或其他地方检索它。如果它使用长时间运行的 CPU 操作计算用户 ID,那么 Task.Run() 是一个不错的选择,您可能还需要在 Task.Run() 的参数中另外指定它是一个长时间运行的任务。

【讨论】:

  • 我不是在计算 id,我正在寻找具有特定 ID 的数据库中的用户,也许该数据库有数百万用户并且在 Id 上没有索引
  • @ProfK:既然你的数据库后端不支持异步任务,你为什么要首先尝试让你的GetByIdAsync 方法async?如果这是为了让调用者可以在该任务运行时继续执行另一项任务,则让调用者有责任调用Task.Run()。 (请参阅blog.stephencleary.com/2013/11/…)您是否只是对代码进行面向未来的验证,希望有一天这将是异步的?在这种情况下,return Task.FromResult( _userService.GetById(id));。否则,根本没有理由使用async,所以不要。
【解决方案3】:

Task.Run() 应该只用于 CPU 密集型工作。不过不太记得为什么。 尝试创建一个 GetByIdAsync () 方法,最终调用异步资源。

【讨论】:

  • 我没有异步资源,该资源是 NHibernate,据我所知还不支持异步。
猜你喜欢
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-17
  • 2012-10-21
  • 2019-07-02
相关资源
最近更新 更多