我需要使用提供的异步不友好库订阅第三方系统的更新(他们在不久的将来将不支持异步)。简化的订阅方法需要委托
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
进行订阅,我已经探索了以下选项,但由于它们的局限性,似乎没有一个被广泛推荐,我陷入了困境:
将处理程序返回类型更改为 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
}
}
在内部异步调用上使用
Result
将处理程序更改为同步方法:
void onTemperatureChangeAsync(float newTemperature)
{
// Result() is a bad practice
anotherLibrary.WriteTemperature(newTemperature).Result();
}
如何克服这个问题? 我认为我不是第一个在迁移到异步世界期间需要解决此类问题的人。
编辑:向
async void
添加了更多等待的调用,以演示为什么处理程序内部需要等待(即使无法等待 async void)并删除错误的注释,正如答案所指出的,async void 是完全不好的做法。
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)
必须遵循一些准则:
onTemperatureChangeAsync
。WeatherServer
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
的建议不适用,但这将是边缘情况,甚至可能是设计中的差距......