C# 异步命令模式-异常处理

C# 异步命令模式-异常处理,c#,.net,exception-handling,design-patterns,C#,.net,Exception Handling,Design Patterns,我正在为客户机/服务器应用程序中的“客户机”类实现一个异步命令模式。我在过去做过一些套接字编码,我喜欢socket/SocketAsyncEventArgs类中使用的新异步模式 我的异步方法如下所示:public bool ExecuteAsync(Command cmd)如果执行挂起,则返回true;如果同步完成,则返回false我的问题是:即使发生异常,我是否应该始终调用回调(cmd.OnCompleted)?还是应该直接从ExecuteAsync抛出异常? 如果你需要,这里有更多的细节。这

我正在为客户机/服务器应用程序中的“客户机”类实现一个异步命令模式。我在过去做过一些套接字编码,我喜欢socket/SocketAsyncEventArgs类中使用的新异步模式

我的异步方法如下所示:
public bool ExecuteAsync(Command cmd)如果执行挂起,则返回true;如果同步完成,则返回false我的问题是:即使发生异常,我是否应该始终调用回调(cmd.OnCompleted)?还是应该直接从ExecuteAsync抛出异常?

如果你需要,这里有更多的细节。这类似于使用SocketAsyncEventArgs,但我的类不是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();
}
类连接
{
public bool ExecuteAsync(命令cmd)
{
///考虑一下:如果你没有抓住这里的每一个异常
///那么此方法的每个调用方必须有2组
///异常处理:
///一个在Command.OnCompleted的处理程序中,另一个在ExecuteAsync中
///被称为。
尝试
{
///这里有一些可能的例外情况:
///1)远程处理。当另一侧断开连接(WCF)时发生。
///2)我在TrackCommand中出错(我想修复一个bug!)
这个.TrackCommand(cmd);
remote.ServerExecuteAsync(cmd.GetRequest());
返回true;
}
捕获(例外情况除外)
{
///命令同步完成。
cmd.已完成(ex,true);
返回false;
}
}
///这就是当服务器返回响应时被一些魔法调用的东西。
内部CommandExecuteTurn(CommandResponse响应)
{
命令cmd=this.getTracketCommand(response.RequestId);
///命令异步完成。
cmd.已完成(响应,错误);
}
私用遥控器;
}
抽象类命令:EventArgs
{
内部作废已完成(异常ex,bool同步)
{
这个.Exception=ex;
this.CompletedSynchronously=同步;
如果(this.OnCompleted!=null)
{
这个。未完成的(这个);
}
}
内部无效完成(CommandResponse响应,bool同步)
{
这个。反应=反应;
此.Completed(response.ExceptionFromServer,同步)
}
public bool同步完成{get;private set;}
公共事件事件处理程序未完成;
公共异常异常{get;private set;}
内部受保护的抽象命令请求GetRequest();
}

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

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

从调度点引发异常可能有用,也可能无用

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


第二次回调用于异常报告可能有意义,而在一个位置处理异常要容易得多。我将使用以下区别:对于应该处理的异常,在回调中抛出它们。它使类的使用更加简单。对于不应捕获的异常(例如ArgumentException),抛出ExecuteAsync。我们希望未经处理的异常尽快爆发。

一种用于.NET中异步操作的通用模式(至少对于
BackgroundWorker
BeginInvoke()/EndInvoke()
方法对将有一个结果对象,将回调与实际返回值或发生的任何异常分开。回调负责处理异常

一些类似C的伪代码:


private delegate 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("I only understand the number five!");
   }
}

private void EndExecuteCommand(IAsyncResult result)
{
    CommandDelegate del;
    int retVal;

    del = (CommandDelegate)((AsyncResult)result).AsyncDelegate;

    try
    {
        // Here's where we get the return value
        retVal = del.EndInvoke(result);
    }
    catch (InvalidOperationException e)
    {
        // See, we had EndExecuteCommand called, but the exception
        // from the Begin method got tossed here
    }
}
因此,如果调用
ExecuteCommandAsync()
,它会立即返回。
BeginExecuteCommandCommand()
在单独的线程中启动。如果它抛出异常,则在调用
IAsyncResult
上的
EndInvoke()
之前不会抛出该异常(您可以将其强制转换为
AsyncResult
,这是有文档记录的,但是如果强制转换让您不舒服,您可以在状态下传递它。这样,异常处理代码“自然地”放置在您将与方法返回值交互的位置

有关详细信息,请签出有关的详细信息


希望这有帮助。

现在,为什么我没有想到这一点?有趣的是,我以前确实为一个运行一系列查询的类这样做过。请注意,即使在.NET Framework代码中,也不能保证只在EndXYZ上引发异常。例如,在指向端点的WCF客户端服务代理上调用BeginXYZ不存在。将立即引发异常。