如何在异步操作失败时正确通知调用方



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调用(当用户按下"启用"按钮时)。

为了满足EnableFeatureE调用后必须通知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实例消息,我不确定这种方法是否合适。如果更多的评论者可以对此提供反馈,我会很高兴。

最新更新