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

问题描述:

我想听听有关处理与Command模式异步操作的最佳方式的意见。假设我们有下面的例子:在C#中处理异步操作的命令模式和异步操作

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事件和make命令订阅他们(似乎臭我)

为了总结的东西了,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:使命令管理所有与异步相关的代码不是一个选项:)。

+0

你怎么能说你不知道是否_myReceiver.DoSomething();有一些异步属性。它要么完成,所以你知道它已经完成了,要么它会返回一些让你控制异步行为的东西。无论哪种方式,_myReceiver.DoSomething()都可能有问题。而不是你的命令。 – Euphoric 2012-01-05 21:25:59

+0

Async除了撤消部分看起来如何 - 命令一个UndoExecution或...? – 2012-01-05 21:27:20

+0

@Euphoric的DoSomething方法可以.BeginInvoke的东西... – 2012-01-05 21:29:20

像这样的事情?

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

这样一来,该命令的用户可以注册,所以它知道当命令已经完成它的异步行为,可以acordingly采取行动,完成事件。

或者如果你不想使用事件,那么回调呢?

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通知其调用者它已完成。没有办法绕过它。

+0

谢谢您的回答,但问题与指示命令完成无关。为了简单起见,我实际上忽略了这部分。我希望看到在Receiver中使用事件是不必要的方法。在你的例子中,我仍然不得不通过事件来处理每个异步操作的垃圾邮件。 – ferspanghero 2012-01-05 21:42:23

+0

@ferspan:除非在MyReciever和其他所有方面都有共同的抽象,否则将以相同的方式使用,那么我没有看到可能的解决方案。 这是当前编程中非常大的问题,并且全新的C#版本会针对异步,等待和任务提出此问题。 – Euphoric 2012-01-05 21:49:38

+0

谢谢!你的方法最终与Tigram相似。因此,我问你同样的问题:考虑_myReceiver.DoSomething(Action action)会产生很多异步调用,我如何确保在所有异步调用完成后执行回调? – ferspanghero 2012-01-05 22:04:59

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

其次,我会添加像参数一个Action<T>这将被称为方法异步调用完成。因此,当异步结束时,可以通知此方法调用方。

编辑

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

+0

在哪里我会添加一个动作参数?如果它在BeginExecute中,这将是没有用的,因为_myReceiver.DoSomething()会以相同的方式返回,这使我们无法了解它的异步状态。如果它在_myReveiver.DoSomething()中,我仍然坚持事件解决方案,我发现它更干净。 – ferspanghero 2012-01-05 21:36:42

+0

@ferspan:如果我在那个类中有20个异步方法,并且在其中执行几个异步方法会怎么样。你将如何处理这个问题?每种方法都必须定义*它自己的事件?或者事件会带来一个方法定义? **伊莫**,'行动'模式更清洁 – Tigran 2012-01-05 21:45:55

+0

@ferspan:看到我编辑过的帖子。 – Tigran 2012-01-05 21:57:28

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

首先初始化所有异步调用,并在当前线程上阻塞(等待)以返回调用。我不确定异步调用的本质是什么,就像它们在您知道的线程中,或者将在任意线程中返回一样,但您应该能够创建某种线程同步您的问题。

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

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

我觉得你在单个课堂上的表现太多了。我想打破它,就像这样:

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

这里最大的好处是,你可以不沿着所有与处理程序走的依赖性明确拖动分发您的命令给客户。客户不应该知道处理程序。所有客户端需要知道的是它发送了一个命令,并且所有的命令都应该被假定为成功。

+0

非常好。谢谢你!装饰者的胜利! – trailmax 2013-11-23 00:17:35