如何在C#中的多命令模式中与不同的线程共享相同的上下文?

Gia*_*vas 10 c# multithreading design-patterns command-pattern

有一个扩展的命令模式实现,以支持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);
Run Code Online (Sandbox Code Playgroud)

现在,执行就像:

groups.Execute();
Run Code Online (Sandbox Code Playgroud)

我正在共享相同的上下文(ctx)对象.

Web应用程序的执行计划需要在不同的线程中分离 commandGroup1commandGroup2分组.具体来说,commandGroup2将在新线程和commandGroup1主线程中执行.

执行现在看起来像:

//In Main Thread
commandGroup1.Execute();

//In the new Thread
commandGroup2.Execute();
Run Code Online (Sandbox Code Playgroud)

如何线程安全地共享相同的内容context object (ctx),以便能够commandGroup1从新线程回滚?

是否 t.Start(ctx);足够或我必须使用锁或什么?

一些代码实现示例在这里

Joh*_*ers 1

假设我们有一个 MultiCommand 类,它聚合了 ICommand 列表,并且有时必须异步执行所有命令。所有命令必须共享上下文。每个命令都可以更改上下文状态,但没有固定的顺序!

第一步是启动 CTX 中传递的所有 ICommand 执行方法。下一步是为新的 CTX 更改设置事件侦听器。

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

每个命令处理异步部分类似于:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后我们建立了一个通用的事件处理程序和通知系统。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

每个命令的执行函数中都可以进行进一步的抽象。但我们现在不讨论这个。

以下是此设计的作用和不作用:

  1. 它允许任何已完成的任务更新在 MultiCommand 类中首次设置的线程上的新上下文。
  2. 这假设不需要基于工作流的状态。这篇文章只是指出一堆任务只需要异步运行,而不是以有序的异步方式运行。
  3. 不需要货币管理器,因为我们依赖每个命令的异步任务的关闭/完成来返回其创建的线程上的新上下文!

如果您需要并发性,那么这意味着上下文状态很重要,该设计与此类似但有所不同。使用闭包函数和回调可以轻松实现该设计。