C#中的命令模式和异步操作处理

时间:2012-01-05 21:17:54

标签: c# design-patterns asynchronous

我想听听有关使用Command模式处理异步操作的最佳方法的意见。假设我们有以下示例:

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

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

问题是:MyCommand不知道MyReceiver.DoSomething()是否有异步的代码部分。如果我想在执行后将MyCommand推入撤销堆栈,我无法保证其接收器操作已完全执行,因此无法确定MyCommand是否达到可以撤消的状态。

我个人想到了以下解决方案:

  1. 在Command
  2. 中实现某种状态控制
  3. 在Command
  4. 中包含“BeginExecute”和“EndExecute”
  5. 在MyReceiver中包含事件并让命令订阅它们(这对我来说似乎很臭)
  6. 为了解决问题,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
    }
    

    我现在有办法确保Command的接收器已经完成了执行任何操作,并且它已准备好被推入撤消堆栈。但是,对于事件垃圾邮件,每个包含异步操作的Receiver类都会让我感到很烦恼。

    我在互联网上找不到这个主题,并且很想听到不同的方法。

    OBS:使命令管理所有与异步相关的代码不是一个选项:)。

4 个答案:

答案 0 :(得分:2)

我认为你在一堂课中有太多的进展。我会这样打破它:

// 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 :(得分:1)

首先,我将添加方法Async的名称,以明确地向您的Command类消费者发出该方法以异步方式执行的信号。

其次,我会添加类似参数Action<T>,它将作为方法异步调用完成调用。因此,当异步操作终止时,可以通知此方法调用方。

修改

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

答案 2 :(得分:1)

这样的东西?

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

这样,此命令的用户可以注册到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通知其调用者的方式,它就完成了。没有办法绕过它。

答案 3 :(得分:1)

如果要在控制权返回到Execute方法之前强制要求所有处理都已完成,而不修改调用代码的行为,则可以修改执行操作的方式。

首先初始化所有异步调用并在当前线程上阻塞(等待)以返回调用。我不确定你的异步调用的性质是什么,例如它们是否在你知道的线程中,或者将在任意线程上返回,但你应该能够提出某种线程同步你的问题。

尝试使用Semaphore来阻止当前线程(在调用异步方法之后),并在所有异步方法都返回其响应时释放信号量。这将具有“重新同步”异步调用的效果。

您可以使用其他同步方法,但信号量很容易理解。