异步命令模式 - 异常处理

问题描述:

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

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

这里有一些更多的细节,如果你需要它们。这与使用SocketAsyncEventArgs类似,但我们的类名为SomeCmd,而不是SocketAsyncEventArgs。

SomeCmd cmd = new SomeCmd(23, 14, 10, "hike!"); 
cmd.OnCompleted += this.SomeCmd_OnCompleted; 
this.ConnectionToServer.ExecuteAsync(cmd); 

与Socket类,如果你需要与你的回调方法(在这种情况下SomeCmd_OnCompleted)协调,则可以使用ExecuteAsync的返回值知道操作是否挂起(真),或者如果操作同步完成。

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(); 
} 

从调度点抛出异常可能会或可能不会有用

调用回调传递一个异常参数要求完成回调做2件不同的事情

第二回调异常报告可能是有意义而不是

+0

现在,为什么我没有想到这一点吗?有趣的是,我之前已经为一个运行一系列查询的类实现了这个功能 – 2008-11-18 03:15:02

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

我会不是在ExecuteAsync中引发异常,而是设置回调的异常条件。这将为异步逻辑创建一致的编程方式,并减少重复代码。客户可以调用这个类并期待一种处理异常的方法。这将提供更少的bug,更脆弱的代码。

在一个地方处理异常要容易得多。我将使用以下区别:对于应该处理的异常,将它们放在回调中。它使得使用该类更简单。对于不应捕获的异常(例如,ArgumentException),请引入ExecuteAsync。我们希望未处理的异常尽快炸毁。

在.NET(异步操作,至少在BackgroundWorkerBeginInvoke()/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(),它立即返回BeginExecuteCommand()在一个单独的线程启动。如果它抛出一个异常,那么除非你在IAsyncResult上调用EndInvoke()(你可以投到AsyncResult,这是有记录的,但如果演员让你不舒服,你可以在状态中传递它)。异常处理代码是“自然地安置”,围绕在那里你将与方法的返回值进行交互。

欲了解更多信息,结账更多信息the IAsyncResult pattern on MSDN

希望这有助于。

+0

请注意,即使在.NET Framework代码中,也只能在EndXYZ上抛出异常。例如,在指向不存在的端点的WCF客户端服务代理上调用BeginXYZ。异常立即抛出。 – 2009-05-29 11:24:07