【问题标题】:Async command pattern - exception handling异步命令模式 - 异常处理
【发布时间】:2008-11-18 02:25:17
【问题描述】:

我正在为客户端/服务器应用程序中的“客户端”类实现异步命令模式。我过去做过一些套接字编码,我喜欢他们在 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();
}

【问题讨论】:

    标签: c# .net exception-handling design-patterns


    【解决方案1】:

    .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 的更多信息。

    希望这会有所帮助。

    【讨论】:

    • 请注意,异常只会在 EndXYZ 上抛出,不能保证,即使在 .NET Framework 代码中也是如此。例如,在指向不存在的终结点的 WCF 客户端服务代理上调用 BeginXYZ。立即抛出异常。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      调用传递异常参数的回调需要完成回调做两件不同的事情

      异常报告的第二个回调可能更有意义

      【讨论】:

      • 现在,我怎么没想到呢?有趣的是,我之前实际上已经为一个运行一系列查询的类做过这个。
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        在一处处理异常要容易得多。我会使用以下区别:对于应该处理的异常,将它们扔到回调中。它使使用类变得更简单。对于不应被捕获的异常(例如,ArgumentException),在 ExecuteAsync 中抛出。我们希望尽快处理未处理的异常。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-08-29
          • 1970-01-01
          • 2022-10-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多