异步处理程序与非异步订阅发生冲突

问题描述 投票:0回答:1

我需要使用提供的异步不友好库订阅第三方系统的更新(他们在不久的将来将不支持异步)。简化的订阅方法需要委托

Action<float> onTemperatureChange
:

// Third party library
class WeatherServer
{
    // I cannot change signature of this method (it is third party)
    void SubscribeTemperature(Action<float> onTemperatureChange);
}

我可以使用同步方法轻松使用此类 API:

void onTemperatureChange(float newTemperature)
{
    // Synchronous temperature handling
}

weatherServer.SubscribeTemperature(onTemperatureChange);

如果我需要像这样的异步温度处理程序,推荐的方法是什么:

async Task onTemperatureChangeAsync(float newTemperature)
{
    var feelsLikeTemperature = await anotherLibrary1.GetFeelsLikeTemperature(newTemperature);   
    await  anotherLibrary1.WriteTemperature(feelsLikeTemperature);   
}

请注意,上面的示例已大大简化,内部处理程序逻辑是充满等待的异步方法的复杂逻辑。

考虑到我无法更改班级

WeatherServer
并且我必须通过
Action<float> onTemperatureChange
进行订阅,我已经探索了以下选项,但由于它们的局限性,似乎没有一个被广泛推荐,我陷入了困境:

  1. 将处理程序返回类型更改为 async void

    async void onTemperatureChangeAsync(float newTemperature)
    :

    // async void not propagating/throwing any exception
    async void onTemperatureChangeAsync(float newTemperature)
    {
        // asynchronous temperature handling (for example):
        try
        {
            var feelsLikeTemperature = await anotherLibrary1.GetFeelsLikeTemperature(newTemperature);   
            await anotherLibrary1.WriteTemperature(feelsLikeTemperature);   
        }
        catch(Exception exc)
        {
            // Exception recovery handling not rethrowing/propagating any exception
        }
    }
    
  2. 在内部异步调用上使用

    Result
    将处理程序更改为同步方法:

    void onTemperatureChangeAsync(float newTemperature)
    {
        // Result() is a bad practice
        anotherLibrary.WriteTemperature(newTemperature).Result();    
    }
    

如何克服这个问题? 我认为我不是第一个在迁移到异步世界期间需要解决此类问题的人。

编辑:向

async void
添加了更多等待的调用,以演示为什么处理程序内部需要等待(即使无法等待 async void)并删除错误的注释,正如答案所指出的,async void 是完全不好的做法。

c# asynchronous async-await
1个回答
0
投票

async void
是正确的选择,也是为处理程序推荐的解决方案。请参阅:https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void

假设

WeatherServer
根本不依赖于订阅者的处理程序内部逻辑。换句话说,
WeatherServer
使用了即发即忘逻辑。在再次触发之前,不依赖处理程序完成其内部处理。

处理程序

async void onTemperatureChangeAsync(float newTemperature)
必须遵循一些准则:

  • 不应引发/传播异常。 async void 方法引发的异常无法被启动 async void 工作的方法捕获。换句话说,最好在处理程序中处理任何异常
    onTemperatureChangeAsync
  • 必须能够处理来自
    WeatherServer
  • 的潜在并发来电
  • Async void 方法很难测试,因此实现适当的
    async Task onTemperatureChangeAsync(float newTemperature)
    方法可能是个好主意,该方法将进行单元测试并
    async void wrapper
    用作处理程序
// Proper Async method (used for unit testing)
async Task onTemperatureChangeAsync(float newTemperature)
{
    try
    {
        var feelsLikeTemperature = await anotherLibrary1.GetFeelsLikeTemperature(newTemperature);   
        await anotherLibrary1.WriteTemperature(feelsLikeTemperature);   
    }
    catch(Exception exc)
    {
        log.Error("Handler failed");
    }
}

// Handler
async void onTemperatureChangeVoidAsync(float newTemperature)
{
   await onTemperatureChangeAsync(newTemperature)
}


weatherServer.SubscribeTemperature(onTemperatureChangeVoidAsync);

我已经根据 @Aditive 提示/评论和 https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous 编译了答案-编程#避免异步void

欢迎任何进一步的提示。我会尝试为此答案添加有用的提示。

如果

WeatherServer
应该在继续之前等待处理程序,或者甚至应该捕获处理程序抛出的异常,则上述使用
async void
的建议不适用,但这将是边缘情况,甚至可能是设计中的差距......

© www.soinside.com 2019 - 2024. All rights reserved.