4

我有一堂课是“经理”类的课。它的功能之一是指示类的长时间运行的进程应该关闭。它通过在类中设置一个名为“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 任务使用的类,然后将该令牌传递给这些类。

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

编辑:

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

编辑2:

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

4

6 回答 6

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)把它转过来并使用观察者模式——这将使你的从属对象与父对象分离。如果我是手动完成的(而不是使用事件),我会修改我的从属类以实现一个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();
    }
}
于 2010-06-10T13:08:57.040 回答
0

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

于 2010-06-10T06:54:44.287 回答
0

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

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

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

或者,您可以创建一个所有类都知道的某种上下文,以及他们可以在哪里检查取消的标志。

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

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

于 2010-06-10T12:56:30.430 回答
0

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

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

赶上OperationCanceledException顶层。

这并没有真正的性能损失,因为 (a) 它不会经常发生,并且 (b) 当它确实发生时,它只会发生一次。

此方法还可以很好地与 WinFormsBackgroundWorker组件结合使用。工作线程将自动捕获工作线程中抛出的异常并将其封送回 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?
    }
}
于 2010-06-10T13:38:44.937 回答
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();
    }
}

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

于 2010-06-10T14:03:03.940 回答
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());
    }
}

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

于 2010-06-11T02:43:43.490 回答