我想听听有关使用Command模式处理异步操作的最佳方法的意见。假设我们有以下示例:
public class MyCommand
{
// Sets up receiver and does whatever stuff
public void Execute()
{
_myReceiver.DoSomething();
}
}
问题是:MyCommand不知道MyReceiver.DoSomething()是否有异步的代码部分。如果我想在执行后将MyCommand推入撤销堆栈,我无法保证其接收器操作已完全执行,因此无法确定MyCommand是否达到可以撤消的状态。
我个人想到了以下解决方案:
为了解决问题,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 :(得分: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来阻止当前线程(在调用异步方法之后),并在所有异步方法都返回其响应时释放信号量。这将具有“重新同步”异步调用的效果。
您可以使用其他同步方法,但信号量很容易理解。