There is an extended implementation of command pattern to support multi-commands (groups) in C#:
var ctx= //the context object I am sharing...
var commandGroup1 = new MultiItemCommand(ctx, new List<ICommand>
{
new Command1(ctx),
new Command2(ctx)
});
var commandGroup2 = new MultiItemCommand(ctx, new List<ICommand>
{
new Command3(ctx),
new Command4(ctx)
});
var groups = new MultiCommand(new List<ICommand>
{
commandGroup1 ,
commandGroup2
}, null);
Now , the execution is like:
groups.Execute();
I am sharing the same context (ctx) object.
The execution plan of the web app needs to separate
commandGroup1
and commandGroup2
groups in different thread. In specific, commandGroup2
will be executed in a new thread and commandGroup1
in the main thread.
Execution now looks like:
//In Main Thread
commandGroup1.Execute();
//In the new Thread
commandGroup2.Execute();
How can I thread-safely share the same context object (ctx)
, so as to be able to rollback the commandGroup1
from the new Thread ?
Is t.Start(ctx);
enough or do I have to use lock or something?
Some code implementation example is here
Assume we have a MultiCommand class that aggregates a list of ICommands and at some time must execute all commands Asynchronously. All Commands must share context. Each command could change context state, but there is no set order!
The first step is to kick off all ICommand Execute methods passing in the CTX. The next step is to set up an event listener for new CTX Changes.
public class MultiCommand
{
private System.Collections.Generic.List<ICommand> list;
public List<ICommand> Commands { get { return list; } }
public CommandContext SharedContext { get; set; }
public MultiCommand() { }
public MultiCommand(System.Collections.Generic.List<ICommand> list)
{
this.list = list;
//Hook up listener for new Command CTX from other tasks
XEvents.CommandCTX += OnCommandCTX;
}
private void OnCommandCTX(object sender, CommandContext e)
{
//Some other task finished, update SharedContext
SharedContext = e;
}
public MultiCommand Add(ICommand cc)
{
list.Add(cc);
return this;
}
internal void Execute()
{
list.ForEach(cmd =>
{
cmd.Execute(SharedContext);
});
}
public static MultiCommand New()
{
return new MultiCommand();
}
}
Each command handles the asynchronous part similar to this:
internal class Command1 : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public async void Execute(object parameter)
{
var ctx = (CommandContext)parameter;
var newCTX = await Task<CommandContext>.Run(() => {
//the command context is here running in it's own independent Task
//Any changes here are only known here, unless we return the changes using a 'closure'
//the closure is this code - var newCTX = await Task<CommandContext>Run
//newCTX is said to be 'closing' over the task results
ctx.Data = GetNewData();
return ctx;
});
newCTX.NotifyNewCommmandContext();
}
private RequiredData GetNewData()
{
throw new NotImplementedException();
}
}
Finally we set up a common event handler and notification system.
public static class XEvents
{
public static EventHandler<CommandContext> CommandCTX { get; set; }
public static void NotifyNewCommmandContext(this CommandContext ctx, [CallerMemberName] string caller = "")
{
if (CommandCTX != null) CommandCTX(caller, ctx);
}
}
Further abstractions are possible in each Command's execute function. But we won't discuss that now.
Here's what this design does and doesn't do:
If you need concurrency then that implies that the context state is important, that design is similar to this one but different. That design is easily implemented using functions and callbacks for the closure.