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

本文关键字:异步操作 处理 模式 命令 | 更新日期: 2023-09-27 18:19:38

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

public class MyCommand 
{
   // Sets up receiver and does whatever stuff
   public void Execute()
   {  
       _myReceiver.DoSomething();
   } 
}

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

我个人认为以下解决方案:

  1. 在Command中实现某种状态控制
  2. 在命令中包含"BeginExecute"answers"EndExecute"
  3. 在MyReceiver中包含事件并使Command订阅它们(对我来说这似乎很臭)

最后,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:让命令管理所有异步相关的代码不是一个选项:)。

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

我认为一个类中发生的事情太多了。我会这样分解:

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

这里的最大优势是,您可以将命令分发到客户端,而无需显式地拖动处理程序附带的所有依赖项。客户端不应该知道处理程序。客户端只需要知道它发送了一个命令,并且所有命令都应该被认为是成功的。

类似的东西?

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

无论哪种方式,MyReciver都只需要有一种方法来通知它的调用者,它已经完成了。没有办法绕过它。

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

其次,我将添加一个类似参数Action<T>,该参数将在方法异步调用完成时调用。因此,当异步sction终止时,可以通知此方法调用方。

编辑

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

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

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

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

您可以使用另一种同步方法,但Semaphore足够简单,可以理解。