如何在异步操作失败时正确通知调用方
本文关键字:通知 调用 异步操作 失败 | 更新日期: 2023-09-27 18:32:25
Class FeatureManager
管理某些功能,如下所示:
public class FeatureManager
{
public event EventHandler FeatureEnabledChangedEvent;
private void OnFeatureEnabledChanged()
{
if (FeatureEnabledChangedEvent != null)
{
FeatureEnabledChangedEvent(this, EventArgs.Empty);
}
}
public event EventHandler FeatureEnableBusyChangedEvent;
private void OnFeatureEnableBusyChanged()
{
if (FeatureEnableBusyChangedEvent != null)
{
FeatureEnableBusyChangedEvent(this, EventArgs.Empty);
}
}
public event EventHandler FeatureEnableFailedEvent;
private void OnFeatureEnableFailed(FeatureEnableFailedEventArgs args)
{
if (FeatureEnableFailedEvent!= null)
{
FeatureEnableFailedEvent(this, args);
}
}
private bool _isFeatureEnabled
public bool IsFeatureEnabled
{
get
{
return _isFeatureEnabled;
}
private set
{
if (_isFeatureEnabled != value)
{
_isFeatureEnabled = value;
OnFeatureEnabledChanged();
}
}
}
private bool _isFeatureEnableBusy;
public bool IsFeatureEnableBusy
{
get
{
return _isFeatureEnableBusy;
}
private set
{
if (_isFeatureEnableBusy != value)
{
_isFeatureEnableBusy = value;
OnFeatureEnableBusyChanged();
}
}
}
public async Task EnableFeature()
{
IsFeatureEnableBusy = true;
try
{
// By its nature, process of enabling this feature is asynchronous.
await EnableFeatureImpl(); // can throw exception
IsFeatureEnabled = true;
}
catch(Exception exc)
{
OnFeatureEnableFailed(new FeatureEnableFailedEventArgs(exc.Message));
}
finally
{
IsFeatureEnableBusy = false;
}
}
}
在以下情况下,必须通知 UI 类FeatureView
:
-
IsFeatureEnableBusy
更改(或者,换句话说,当执行EnableFeature
时 - 为了禁用某些控件) -
IsFeatureEnabled
变化 -
EnableFeature
失败(当它引发异常时,在这种情况下FeatureView
显示错误消息给用户)
EnableFeature
可以从某些引擎类E
调用(在应用程序启动时初始化期间自动调用),也可以从FeatureView
调用(当用户按下"启用"按钮时)。
为了满足EnableFeature
被E
调用后必须通知FeatureView
的要求,我添加了一个事件FeatureEnableFailedEvent
。
当E
调用EnableFeature
并且EnableFeature
引发异常时,FeatureView
会收到FeatureEnableFailedEvent
并显示错误消息。但是,当FeatureView
本身调用EnableFeature
并且EnableFeature
失败时,FeatureView
会捕获抛出的异常,但也会收到有关此失败的通知FeatureEnableFailedEvent
因此错误处理程序被调用两次。如何避免这种情况?
一种解决方案是将EnableFeature
声明为旧式异步方法(并使用 BackgroundWorker),如以下代码片段所示:
public class FeatureManager
{
public void EnableFeatureAsync()
{
var bgw = new BackgroundWorker();
bgw.DoWork += (sender, e) =>
{
IsFeatureEnableBusy = true;
EnableFeatureImpl(); // can throw exception
};
bgw.RunWorkerCompleted += (sender, args) =>
{
IsFeatureEnableBusy = false;
if (args.Error == null)
{
IsFeatureEnabled = true;
}
else
{
OnFeatureEnableFailed(new FeatureEnableFailedEventArgs(args.Error.Message));
}
};
bgw.RunWorkerAsync();
}
}
在这种情况下,EnableFeatureAsync
的调用方可以假定此方法异步运行(方法名称中的后缀 Async 应该是提示),并且如果要在方法失败时收到通知,它必须订阅FeatureEnableFailedEvent
。这样FeatureView
只会收到一次失败EnableFeatureAsync
通知,因此错误处理程序按原样调用一次。
这是一个好方法吗?这可以通过以某种方式仍然使用 async/await 来实现吗?假设方法名称中的后缀 Async 对调用者来说是一个足够好的提示,以便他们知道此方法作为异步方法运行并且他们必须查找一些要订阅的事件,这是否很好?
正如@svick所评论的那样,我也不明白为什么你的FeatureView
会捕获异常并获取事件,当异常没有在FeatureManager
处理程序中重新抛出时。但这里有一种不同的方法,根据事件,我更喜欢你的方法:
使用 TaskCompletionSource 让视图知道启用功能何时引发异常,即使FeatureView
不是EnableFeature()
的调用方(顺便说一句,按照惯例,该方法在第一个例子中也应命名为 EnableFeatureAsync)。
public class FeatureManager
{
public TaskCompletionSource<bool> FeatureCompleted { get; private set; }
// if you still need this property
public bool IsFeatureEnabled
{
get { return FeatureCompleted.Task.IsCompleted; }
}
public FeatureManager() {}
public async Task EnableFeature()
{
IsFeatureEnableBusy = true;
try
{
// By its nature, process of enabling this feature is asynchronous.
await EnableFeatureImpl(); // can throw exception
this.FeatureCompleted.TrySetResult(true);
}
catch(Exception exc)
{
this.FeatureCompleted.TrySetException(exc);
}
finally
{
IsFeatureEnableBusy = false;
}
}
}
您的FeatureView
实例现在需要等待任务完成源的任务。代码可能如下所示:
public class FeatureView
{
// if you still need this property
public async void HandleFeatureCompleted(FeatureManager fm)
{
try
{
await fm.FeatureCompleted.Task;
}
catch(Exception e)
{
// handle exception
}
}
}
您必须为视图提供正确的FeatureManager
实例。如果您拥有百分之一甚至数千条FeatureManager
实例消息,我不确定这种方法是否合适。如果更多的评论者可以对此提供反馈,我会很高兴。