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

时间:2014-11-30 00:41:19

标签: c# asynchronous async-await

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(当用户按下&#39时)调用;启用'按钮)。

为了满足FeatureViewEnableFeature调用后E失败时必须通知的要求,我添加了一个事件FeatureEnableFailedEvent

E调用EnableFeatureEnableFeature引发异常时,FeatureView会收到FeatureEnableFailedEvent并显示错误消息。但是当FeatureView本身调用EnableFeatureEnableFeature失败时,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 对调用者来说是一个足够好的提示,因此他们知道这个方法以异步方式运行,并且他们必须查找一些事件来订阅吗

1 个答案:

答案 0 :(得分:0)

正如@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实例现在需要等待TaskCompletionSource的任务。代码可能如下所示:

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实例消息,我不确定这种方法是否合适。如果评论者中有更多人可以就此提供反馈,我会很高兴。