Шаблон асинхронной команды - обработка исключений - PullRequest
3 голосов
/ 18 ноября 2008

Я реализую шаблон асинхронной команды для класса "клиент" в приложении клиент / сервер. В прошлом я занимался кодированием сокетов, и мне нравится новый шаблон Async, который они использовали в классах 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();
}

Ответы [ 5 ]

5 голосов
/ 18 ноября 2008

Один общий шаблон для асинхронных операций в .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(), он немедленно вернется. BeginExecuteCommand() запускается в отдельном потоке. Если оно выбрасывает исключение, оно не будет выдано, пока вы не вызовете EndInvoke() для IAsyncResult (который вы можете привести к AsyncResult, что задокументировано, но вы можете передать его в состоянии, если приведение произвело вас неудобно. Таким образом, код обработки исключений «естественно размещается» там, где вы будете взаимодействовать с возвращаемым значением метода.

Для получения дополнительной информации ознакомьтесь с дополнительной информацией о шаблоне IAsyncResult на MSDN .

Надеюсь, это поможет.

4 голосов
/ 18 ноября 2008

Я бы не сгенерировал бы исключение в ExecuteAsync и вместо этого установил бы условие исключения для обратного вызова. Это создаст согласованный способ программирования против асинхронной логики и сократит повторяющийся код. Клиент может вызвать этот класс и ожидать один из способов обработки исключений. Это даст меньше глючного, менее хрупкого кода.

3 голосов
/ 18 ноября 2008

выбрасывание исключения из пункта отправления может или не может быть полезным

вызов обратного вызова с передачей аргумента исключения требует, чтобы обратный вызов завершения сделал две разные вещи

вместо этого может иметь смысл второй обратный вызов для отчетов об исключениях

1 голос
/ 18 ноября 2008

Я бы выбрасывал пользовательское исключение и не вызывал завершенный обратный вызов. В конце концов, команда не была выполнена, если возникла исключительная ситуация.

0 голосов
/ 18 ноября 2008

Обработка исключений в одном месте намного проще. Я бы использовал следующее различие: для исключений, которые должны быть обработаны, добавьте их в обратный вызов. Это упрощает использование класса. Для исключений, которые не должны быть перехвачены (например, ArgumentException), добавьте ExecuteAsync. Мы хотим, чтобы необработанные исключения взорвались как можно скорее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...