【问题标题】:Convert conventional method to Asynchronous将常规方法转换为异步
【发布时间】:2018-10-11 20:17:36
【问题描述】:

我有一个 API 负责在数据库中插入文本消息详细信息。 它通过对存储库进行同步调用来实现,我认为这可以异步实现。我怎样才能实现这一点?或者什么是处理这种情况的最佳方法。代码 sn-p 示例受到高度赞赏,因为我仍然在围绕 .NET 前进。

api:

public IHttpActionResult SendSMSNotification([FromBody] SMSNotification smsNotification)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }            
        _service.SendSMS(smsNotification);

        return Ok();
    }

服务:

internal void SendSMS(SMSNotification smsNotification)
    {
        _repository.Notify(_mapperService.GetSMSNotification(smsNotification));
    }

映射器:

public SMSNotification GetSMSNotification(SMSNotification message)
    {
        return AutoMapper.Mapper.Map<SMSNotification>(message); 
    }

回购:

public virtual bool Notify(SMSNotification request)
    {
        using (var sql = _sqlMapper.CreateCommand('Database', 'Stored proc'))
        {
            sql.AddParam("@fMessage", request.Message);
           //..............
           //.............. more params
            var retvalParamOutput = sql.AddOutputParam("@fRetVal", System.Data.SqlDbType.Int);
            sql.Execute();
            return retvalParamOutput.GetSafeValue<int>() == 1;
        }
    }

这里的sql是一个自定义的东西,它有以下方法:

  public static int Execute(this IDataCommand @this);
        [AsyncStateMachine(typeof(<ExecuteAsync>d__1))]
        public static Task<int> ExecuteAsync(this IDataCommand @this);

【问题讨论】:

  • sql 在您的 Notify 方法中是什么类型?
  • 如果您使用的 I/O 对象支持异步,则可以将其设为异步。你用的是什么数据库?您使用哪些类与该数据库进行交互。
  • 它是一个在现有代码库中使用的自定义类,在本例中为 SQL Server 中的普通 sql 任务提供了便利。
  • 有没有办法可以从 Api 异步调用 _service.SendSMS(smsNotification)?
  • 或者可以转成Task吗?

标签: c# asp.net .net asp.net-web-api async-await


【解决方案1】:

将阻塞的、通常受 IO 绑定的调用(例如数据库、网络或文件系统工作)更改为异步可以使您的应用更好地扩展。

这确实会通过您的 API 产生持续影响。也就是说,您需要一直等待异步调用直到最顶层的调用,否则,某处会阻塞,您将失去调用异步 API 的好处。

为了证明这一点,让我们从存储库调用的底部开始,因为这可能是昂贵的阻塞操作可以异步进行。我们将 sql.Execute 改为使用异步版本 ExecutAsync 版本:

回购:

public virtual async Task<bool> Notify(SMSNotification request)
{
    using (var sql = _sqlMapper.CreateCommand('Database', 'Stored proc'))
    {
        sql.AddParam("@fMessage", request.Message);
       //..............
       //.............. more params
        var retvalParamOutput = sql.AddOutputParam("@fRetVal", System.Data.SqlDbType.Int);
        await sql.ExecuteAsync();
        return retvalParamOutput.GetSafeValue<int>() == 1;
    }
}

现在我们必须更改方法的签名以返回一个包含 bool 结果的 Task。

我们还将该方法标记为异步,因此我们可以进一步使用“await”运算符。如果不这样做,我们将不得不做更多的重构来自己操作和返回任务结果,但是“async”修饰符和“await”关键字让编译器为我们做这个魔术,我们的其余代码大多看起来像正常。

映射器调用实际上不需要更改:

映射器:

public SMSNotification GetSMSNotification(SMSNotification message)
{
    return AutoMapper.Mapper.Map<SMSNotification>(message); 
}

服务调用现在正在调用异步方法,所以因为我们想要等待而不阻塞该异步调用,所以我们还必须将这个以前的 void 方法更改为异步方法。请注意,我们将其从“void”更改为“async Task”;您可以将 void 方法标记为“async void”,但这旨在作为 Windows 窗体和 WPF 应用程序中的事件处理程序的一种解决方法;在其他所有情况下,您都希望在使其异步时将“void”方法更改为“async Task”。

服务:

internal async Task SendSMS(SMSNotification smsNotification)
{
    await _repository.Notify(_mapperService.GetSMSNotification(smsNotification));
}

最后,我们的 API 调用可以异步,等待我们的服务调用:

api:

public async Task<IHttpActionResult> SendSMSNotification([FromBody] SMSNotification smsNotification)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }            
    await _service.SendSMS(smsNotification);

    return Ok();
}

有时建议在您进行这样的重构之后,按照约定重命名方法以“异步”结尾;但是我不认为这真的是强制性的,因为很多 .NET API 表面正在变得异步,现在几乎是多余的。

尽管如此,还是值得关注 async / await 的东西;我试图让这个例子相对简短。但我希望这至少能让你开始。

【讨论】:

  • 非常感谢您提供如此清晰的解释。这正是我正在寻找的东西。网上一些冗长的文章让我无处可去。是时候拿起一本关于这方面的坚实基础书籍了。再次非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-12
  • 1970-01-01
  • 1970-01-01
  • 2019-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多