Шаблон команды и обработка асинхронных операций в C # - PullRequest
4 голосов
/ 06 января 2012

Мне бы хотелось услышать мнения о наилучшем способе обработки асинхронных операций с помощью шаблона Command.Допустим, у нас есть следующий пример:

public class MyCommand 
{
   // Sets up receiver and does whatever stuff

   public void Execute()
   {  
       _myReceiver.DoSomething();
   } 
}

Проблема в том, что MyCommand не знает, имеет ли MyReceiver.DoSomething () асинхронные части кода.Если бы я хотел поместить MyCommand в стек отмены после его выполнения, я не мог гарантировать, что его действие получателя было полностью выполнено, из-за чего я не мог знать, достигло ли MyCommand состояния, в котором отмена возможна или нет.

Я лично подумал о следующем решении:

  1. Реализация какого-либо контроля состояния в Команде
  2. Включение "BeginExecute" и "EndExecute" в Команду
  3. Включение событийв MyReceiver и заставьте команду подписаться на них (что мне кажется вонючим)

Чтобы обернуть вещи, MyCommand превратился бы в:

public class MyCommand 
{
   public MyCommand(MyReceiver receiver)
   {   
      _myReceiver = receiver;
      _myReceiver.DoSomethingFinished += () => this.EndExecute(); 
   }

   public void BeginExecute()
   {  
       this.EnterExecutionState();

       _myReceiver.DoSomething();
   } 

   public void EndExecute()
   {  
       this.LeaveExecutionState();
   } 

   // State handling related stuff
}

Теперь у меня есть средства, чтобы сделатьуверен, что получатель команды завершил выполнение какого-либо действия, и он готов быть помещенным в стек отмены.Тем не менее, в случае спама по событиям каждый отдельный класс Receiver, содержащий асинхронные операции, действительно доставляет мне неприятности.

Я не нашел много информации об этой теме в Интернете и хотел бы услышать разные подходы.

OBS: заставить команду управлять всем асинхронным кодом, это не вариант:).

Ответы [ 4 ]

2 голосов
/ 28 июля 2012

Я думаю, у вас слишком много всего происходит в одном классе.Я бы разбил его так:

// An immutable command, to be handled in-process.  
// ICommand is a marker interface with no members.
public class DoSomething : ICommand 
{
    public readonly Id;

    public DoSomething(Guid id)
    {
        Id = id;
    }
}

// To be handled out-of-process.
[AsynchronousCommand]
public class DoSomethingThatTakesAReallyLongTime : ICommand
{
    public readonly Id;

    public DoSomethingThatTakesAReallyLongTime(Guid id)
    {
        Id = id;
    }
}

// This guy could take any number of dependencies: ISomethingRepository, DbContext, etc.
// Doesn't matter, but it's probably gonna have dependencies.
public class DoSomethingHandler : IHandler<DoSomething> 
{
    public void Handle(DoSomething command) // IHandler<T>'s only member
    {
        // CRUD or call call a domain method
    }
}

public class CommandService : ICommandService
{
    public void Execute(params ICommand[] commands) // ICommandService's only member
    { 
        foreach(var command in commands)
        {
            var handler = GetHandler(command); // Could use your IOC container.

            if (HasAsyncAttribute())
                new Action(() => handler.Handle(command)).BeginInvoke(null, null);
            else
                handler.Handle(command);
        }
    } 
}

// Something that might consume these
public class SomethingController
{
    private readonly ICommandService _commandService;

    public SomethingController(ICommandService commandService)
    {
        _commandService = commandService;
    }

    [HttpPost]
    public void DoSomething(Guid id)
    {
        _commandService.Execute(new DoSomething(id));
    }

    [HttpPost]
    public void DoSomethingThatTakesAReallyLongTime(Guid id)
    {
        _commandService.Execute(new DoSomethingThatTakesAReallyLongTime(id));
    }
}

Большим преимуществом здесь является то, что вы можете распределять свои команды среди клиентов без явного перетаскивания всех зависимостей, связанных с обработчиками.Обработчики не должны быть известны клиенту.Все, что клиент должен знать, - это то, что он отправил команду, и все команды должны быть выполнены успешно.

1 голос
/ 06 января 2012

Как то так?

public interface ICommand
{
    void Execute();
    event EventHandler Finished;
}

public class MyCommand : ICommand
{
   public MyCommand(MyReceiver receiver)
   {   
      _myReceiver = receiver;
      _myReceiver.DoSomethingFinished += () => Finished(); // dont forget null check here.
   }

   public void Execute()
   {      
       _myReceiver.DoSomething();
   } 

   public event EventHandler Finished;
}

Таким образом, пользователь этой команды может зарегистрироваться на событие Завершено, чтобы он знал, когда команда завершила свое асинхронное поведение, и может действовать соответственно.

Или, если вы не хотите использовать событие, то как насчет обратного вызова?

public class MyCommand : ICommand
{
   public MyCommand(MyReceiver receiver)
   {   
      _myReceiver = receiver;
   }

   public void Execute()
   {      
       _myReceiver.DoSomething(() => Finished()); // dont forget null check here.
   } 

   public event EventHandler Finished;
}

В любом случае, для MyReciever просто необходим способ уведомить вызывающего абонента о том, что он закончил. Обойти это невозможно.

1 голос
/ 06 января 2012

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

Сначала инициализируйте все ваши асинхронные вызовы и заблокируйте (ожидайте) в текущем потоке вызовы для возврата. Я не уверен, какова природа ваших асинхронных вызовов, например, если они находятся в потоке, о котором вы знаете, или будут возвращены в произвольном потоке, но вы должны иметь возможность создать какой-то поток синхронизация для вашей проблемы.

Попробуйте использовать Семафор , чтобы заблокировать текущий поток (после вызова асинхронных методов) и освободить семафор, когда все ваши асинхронные методы вернут свои ответы. Это приведет к повторной синхронизации асинхронных вызовов.

Вы можете использовать другой метод синхронизации, но Семафор достаточно прост для понимания.

1 голос
/ 06 января 2012

Во-первых, я бы добавил к имени метода Async, чтобы явно сообщить вашему потребителю класса Command, что метод выполняется асинхронно.

Во-вторых, я бы добавил параметр типа Action<T>который будет вызываться после завершения асинхронного вызова метода.Таким образом, вызывающий метод может быть уведомлен, когда асинхронная секция была прервана.

Редактировать

obj.DoSomethingAsync(... params, Action<T> onComplete)

...