如何取消深层嵌套的进程

时间:2010-06-10 01:52:21

标签: c# oop refactoring request-cancelling

我有一个类是“经理”类。其中一个功能是发出应该关闭该类的长时间运行过程的信号。它通过在类中设置一个名为“IsStopping”的布尔值来实现。

public class Foo
{
    bool isStoping

    void DoWork() {
        while (!isStopping)
        {
            // do work...
        }
    }
}

现在,DoWork()是一个巨大的功能,我决定重构它,并且作为该过程的一部分,将其中的一部分打破到其他类中。问题是,这些类中的一些还具有长时间运行的函数,需要检查isStopping是否为真。

public class Foo
{
    bool isStoping

    void DoWork() {
        while (!isStopping)
        {
            MoreWork mw = new MoreWork()
            mw.DoMoreWork() // possibly long running
            // do work...
        }
    }
}

我有什么选择?

我考虑通过引用传递isStopping,我不太喜欢,因为它需要有一个外部对象。我更愿意让额外的课程尽可能独立,尽可能不依赖。

我还考虑过制作isStopping属性,然后让它调用一个内部类可以订阅的事件,但这看起来过于复杂。

另一种选择是创建一个“进程取消令牌”类,类似于.net 4 Tasks使用的类,然后将该令牌传递给这些类。

你是如何处理这种情况的?

编辑:

还要考虑到MoreWork可能有一个实例化的EvenMoreWork对象,并在...上调用可能长时间运行的方法。我想我正在寻找的是一种方法,能够在呼叫树中发出任意数量的对象信号,告诉他们停止他们正在做的事情并清理并返回。

EDIT2:

感谢目前为止的回复。似乎对使用的方法没有真正的共识,每个人都有不同的意见。看起来这应该是一种设计模式......

6 个答案:

答案 0 :(得分:5)

你可以在这里走两条路:

1)您已经概述的解决方案:将信令机制传递给您的下级对象:bool(通过ref),父对象本身隐藏在接口中(以下示例中为Foo: IController),或者别的。子对象根据需要检查信号。

// Either in the MoreWork constructor
public MoreWork(IController controller) {
    this.controller = controller;
}

// Or in DoMoreWork, depending on your preferences
public void DoMoreWork(IController controller) {
    do {
        // More work here
    } while (!controller.IsStopping);
}

2)转过身来使用observer pattern - 这样可以让您的下级对象与父级分离。如果我是手工完成(而不是使用事件),我会修改我的下级类来实现IStoppable接口,并让我的经理类告诉他们何时停止:

public interface IStoppable {
    void Stop();
}

public class MoreWork: IStoppable {
    bool isStopping = false;
    public void Stop() { isStopping = true; }
    public void DoMoreWork() {
        do {
            // More work here
        } while (!isStopping);
    }
}

Foo维护一个可停止的停止列表,并在其自己的停止方法中,将它们全部停止:

public void Stop() {
    this.isStopping = true;
    foreach(IStoppable stoppable in stoppables) {
        stoppable.Stop();
    }
}

答案 1 :(得分:0)

我认为触发子类订阅的事件是有意义的。

答案 2 :(得分:0)

您可以在经理类和其他每个工作类上创建一个Cancel()方法。基于接口。

管理器类或实例化其他工作类的类必须将Cancel()调用传播给它们所组成的对象。

最深的嵌套类只会将内部_isStopping bool设置为false,而长时间运行的任务会检查它。

或者,你可以创建一个所有类都知道的某种上下文,以及他们可以检查取消标记的位置。

  

另一个选择是创建一个   “进程取消令牌”类,   类似于.net 4任务使用的话   该令牌将传递给这些类。

我对此并不熟悉,但如果它基本上是一个带有bool属性标志的对象,并且你传入每个类,那么这对我来说似乎是最干净的方式。然后你可以创建一个抽象基类,它有一个构造函数,它接受它并将它设置为私有成员变量。然后您的流程循环可以检查取消。 显然,你必须保留对你传递给你的worker的这个对象的引用,以便可以从你的UI设置它的bool标志。

答案 3 :(得分:0)

在检查停止标志最明智的地方,用这样的语句乱丢你的代码:

if(isStopping) { throw new OperationCanceledException(); }

在顶层抓住OperationCanceledException

对此没有实际的性能损失,因为(a)它不会经常发生,(b)当它确实发生时,它只发生一次。

此方法与WinForms BackgroundWorker组件配合使用也很有效。 worker将自动捕获工作线程中的抛出异常并将其封送回UI线程。您只需检查e.Error属性的类型,例如:

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if(e.Error == null) {
        // Finished
    } else if(e.Error is OperationCanceledException) {
        // Cancelled
    } else {
        // Genuine error - maybe display some UI?
    }
}

答案 4 :(得分:0)

您的嵌套类型可以接受委托(或公开事件)以检查取消条件。然后,您的经理向嵌套类型提供委托,该类型检查其自己的“shouldStop”布尔值。这样,唯一的依赖是NestedType上的ManagerType,无论如何都是你已经拥有的。

class NestedType
{
    // note: the argument of Predicate<T> is not used, 
    //    you could create a new delegate type that accepts no arguments 
    //    and returns T
    public Predicate<bool> ShouldStop = delegate() { return false; };
    public void DoWork()
    {
        while (!this.ShouldStop(false))
        {
            // do work here
        }
    }
}

class ManagerType
{
    private bool shouldStop = false;
    private bool checkShouldStop(bool ignored)
    {
        return shouldStop;
    }
    public void ManageStuff()
    {
        NestedType nestedType = new NestedType();
        nestedType.ShouldStop = checkShouldStop;
        nestedType.DoWork();
    }
}

如果您真的想要,可以将此行为抽象到界面中。

interface IStoppable
{
    Predicate<bool> ShouldStop;
}

此外,您可以让“停止”机制抛出异常,而不仅仅是检查一个布尔值。在经理的checkShouldStop方法中,它可以简单地抛出OperationCanceledException

class NestedType
{
    public MethodInvoker Stop = delegate() { };
    public void DoWork()
    {
        while (true)
        {
            Stop();
            // do work here
        }
    }
}

class ManagerType
{
    private bool shouldStop = false;
    private void checkShouldStop()
    {
        if (this.shouldStop) { throw new OperationCanceledException(); }
    }
    public void ManageStuff()
    {
        NestedType nestedType = new NestedType();
        nestedType.Stop = checkShouldStop;
        nestedType.DoWork();
    }
}

我之前使用过这种技术并发现它非常有效。

答案 5 :(得分:0)

您可以使用命令模式将每个DoWork()调用转换为命令,从而展平您的调用堆栈。在顶层,您维护一个要执行的命令队列(或堆栈,具体取决于命令之间的交互方式)。 “调用”函数被转换为将新命令排入队列。然后,在处理每个命令之间,您可以检查是否取消。像:

void DoWork() {
    var commands = new Queue<ICommand>();

    commands.Enqueue(new MoreWorkCommand());
    while (!isStopping && !commands.IsEmpty)
    {
        commands.Deque().Perform(commands);
    }
}

public class MoreWorkCommand : ICommand {
    public void Perform(Queue<ICommand> commands) {
        commands.Enqueue(new DoMoreWorkCommand());
    }
}

基本上,通过将低级调用堆栈转换为您控制的数据结构,您可以在每次“调用”,暂停,恢复,取消等之间检查内容。