异步命令模式 - 异常处理

3
我正在为客户端/服务器应用程序中的“客户端”类实现异步命令模式。我之前进行过一些套接字编码,喜欢它们在Socket/SocketAsyncEventArgs类中使用的新的异步模式。
我的异步方法是这样的:public bool ExecuteAsync(Command cmd);如果执行挂起,则返回true,如果同步完成,则返回false。我的问题是:即使出现异常,我是否应该始终调用回调(cmd.OnCompleted)?还是应该直接从ExecuteAsync抛出异常?
如果您需要更多详细信息,请看这里。这与使用SocketAsyncEventArgs类类似,但我的类名为SomeCmd。
SomeCmd cmd = new SomeCmd(23, 14, 10, "hike!");
cmd.OnCompleted += this.SomeCmd_OnCompleted;
this.ConnectionToServer.ExecuteAsync(cmd);

与 Socket 类一样,如果您需要与回调方法 (在本例中为 SomeCmd_OnCompleted) 协调,则可以使用 ExecuteAsync 的返回值来确定操作是否挂起 (true) 或操作是否同步完成。
SomeCmd cmd = new SomeCmd(23, 14, 10, "hike!");
cmd.OnCompleted += this.SomeCmd_OnCompleted;
if( this.ConnectionToServer.ExecuteAsync(cmd) )
{
    Monitor.Wait( this.WillBePulsedBy_SomeCmd_OnCompleted );
}

以下是我基础类的大幅简化版,您可以看到它的工作原理:
class Connection
{
    public bool ExecuteAsync(Command cmd)
    {
        /// CONSIDER: If you don't catch every exception here
        /// then every caller of this method must have 2 sets of
                /// exception handling:
        /// One in the handler of Command.OnCompleted and one where ExecuteAsync
        /// is called.
        try
        {
        /// Some possible exceptions here:
        /// 1) remote is disposed. happens when the other side disconnects (WCF).
        /// 2) I do something wrong in TrackCommand (a bug that I want to fix!)
            this.TrackCommand(cmd);
            remote.ServerExecuteAsync( cmd.GetRequest() );
            return true;
        }
        catch(Exception ex)
        {
            /// Command completing synchronously.
            cmd.Completed(ex, true);
            return false;
        }
    }
    /// <summary>This is what gets called by some magic when the server returns a response.</summary>
    internal CommandExecuteReturn(CommandResponse response)
    {
        Command cmd = this.GetTrackedCommand(response.RequestId);
        /// Command completing asynchronously.
        cmd.Completed(response, false);
    }

    private IServer remote;
}

abstract class Command: EventArgs
{
    internal void Completed(Exception ex, bool synchronously)
    {
        this.Exception = ex;

        this.CompletedSynchronously = synchronously;

        if( this.OnCompleted != null )
        {
            this.OnCompleted(this);
        }
    }

    internal void Completed(CommandResponse response, bool synchronously)
    {
        this.Response = response;
        this.Completed(response.ExceptionFromServer, synchronously)
    }

    public bool CompletedSynchronously{ get; private set; }

    public event EventHandler<Command> OnCompleted;

    public Exception Exception{ get; private set; }

    internal protected abstract CommandRequest GetRequest();
}
5个回答

5
在.NET中,异步操作的一般模式(至少对于BackgroundWorker和BeginInvoke()/EndInvoke()方法对)是有一个结果对象,将回调与实际返回值或任何发生的异常分开。回调的责任是处理异常。 一些类似C#的伪代码: 私有委托int CommandDelegate(string number); private void ExecuteCommandAsync() { CommandDelegate del = new CommandDelegate(BeginExecuteCommand); del.BeginInvoke("four", new AsyncCallback(EndExecuteCommand), null); } private int BeginExecuteCommand(string number) { if (number == "five") { return 5; } else { throw new InvalidOperationException("我只理解数字五!"); } } private void EndExecuteCommand(IAsyncResult result) { 命令委托del; int retVal; del = (CommandDelegate)((AsyncResult)result).AsyncDelegate; try { //这里我们得到了返回值 retVal = del.EndInvoke(result); } catch (InvalidOperationException e) { //看,我们调用了EndExecuteCommand,但异常来自Begin方法被抛出到这里 } }
如果您调用ExecuteCommandAsync(),它会立即返回。 BeginExecuteCommand()在单独的线程中启动。 如果它抛出异常,则直到您调用EndInvoke()才会抛出该异常,而您可以将其转换为AsyncResult(这是有文档记录的,但如果转换让您感到不舒服,可以将其传递给状态)。 这样,异常处理代码就“自然地放置”在您与方法的返回值交互的位置周围。
要了解更多信息,请查看MSDN上的IAsyncResult模式的更多信息
希望这可以帮助您。

请注意,即使在.NET Framework代码中,也不能保证仅在无法保证EndXYZ时才引发异常。例如,在指向不存在的端点的WCF客户端服务代理上调用BeginXYZ。该异常会立即抛出。 - Alan Christensen

4

我不会在ExecuteAsync中抛出异常,而是为回调函数设置异常条件。这将创建一种一致的编程方式来处理异步逻辑并减少重复代码。客户端可以调用此类并期望一种处理异常的方式。这将提供更少错误、更少脆弱的代码。


3

从调度点抛出异常可能有用,也可能没有用

调用回调时传递异常参数需要完成回调执行两个不同的操作

另外一个用于异常报告的回调可能更合理


现在想想,为什么我没想到呢?有趣的是,我以前为一个运行一系列查询的课程做过这件事。 - Wayne Bloss

1

我会抛出一个自定义异常,而不是调用完成回调函数。毕竟,如果发生异常,命令就没有完成。


0
在一个地方处理异常会更容易。我会使用以下区分:对于应该被处理的异常,在回调中抛出它们。这样可以让类的使用更简单。对于不应被捕获的异常(例如ArgumentException),在ExecuteAsync中抛出。我们希望未处理的异常立即崩溃。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接