如何取消深度嵌套的进程

发布于 2024-09-05 05:17:18 字数 1067 浏览 5 评论 0原文

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

public class Foo
{
    bool isStoping

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

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

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 对象,它实例化并调用一个可能长时间运行的方法......等等。我想我正在寻找的是一种能够向调用树中的任意数量的对象发出信号的方法,告诉它们停止正在做的事情并清理并返回。

EDIT2:

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

I have a class that is a "manager" sort of class. One of it's functions is to signal that the long running process of the class should shut down. It does this by setting a boolean called "IsStopping" in class.

public class Foo
{
    bool isStoping

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

Now, DoWork() was a gigantic function, and I decided to refactor it out and as part of the process broke some of it into other classes. The problem is, Some of these classes also have long running functions that need to check if isStopping is true.

public class Foo
{
    bool isStoping

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

What are my options here?

I have considered passing isStopping by reference, which I don't really like because it requires there to be an outside object. I would prefer to make the additional classes as stand alone and dependancy free as possible.

I have also considered making isStopping a property, and then then having it call an event that the inner classes could be subscribed to, but this seems overly complex.

Another option was to create a "Process Cancelation Token" class, similar to what .net 4 Tasks use, then that token be passed to those classes.

How have you handled this situation?

EDIT:

Also consider that MoreWork might have a EvenMoreWork object that it instantiates and calls a potentially long running method on... and so on. I guess what i'm looking for is a way to be able to signal an arbitrary number of objects down a call tree to tell them to stop what they're doing and clean up and return.

EDIT2:

Thanks for the responses so far. Seems like there's no real consensus on methods to use, and everyone has a different opinion. Seems like this should be a design pattern...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

浪推晚风 2024-09-12 05:17:18

您可以在这里采用两种方法:

1)您已经概述的解决方案:将信号机制传递给您的从属对象:bool(通过引用),父对象本身隐藏在接口中(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< /code> 维护其可停止项的列表,并在其自己的 stop 方法中将它们全部停止:

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

You can go two ways here:

1) The solution you've already outlined: pass a signaling mechanism to your subordinate objects: a bool (by ref), the parent object itself cloaked in an interface (Foo: IController in the example below), or something else. The child objects check the signal as needed.

// 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) Turn it around and use the observer pattern - which will let you decouple your subordinate objects from the parent. If I were doing it by hand (instead of using events), I'd modify my subordinate classes to implement an IStoppable interface, and make my manager class tell them when to stop:

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 maintains a list of its stoppables and in its own stop method, stops them all:

public void Stop() {
    this.isStopping = true;
    foreach(IStoppable stoppable in stoppables) {
        stoppable.Stop();
    }
}
凉月流沐 2024-09-12 05:17:18

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

I think firing an event that your subclasses subscribe to makes sense.

似最初 2024-09-12 05:17:18

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

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

然后,最深的嵌套类将内部 _isStopping bool 设置为 false,并且您的长时间运行的任务将检查这一点。

或者,您可以创建一个所有类都知道的某种上下文,并且它们可以在其中检查已取消的标志。

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

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

You could create a Cancel() method on your manager class, and on each of your other worker classes. Base it on an interface.

The manager class, or classes that instantiate other worker classes, would have to propagate the Cancel() call to the objects they are composed of.

The deepest nested classes would then just set an internal _isStopping bool to false and your long-running tasks would check for that.

Alternatively, you could maybe create a context of some sort that all the classes know about and where they can check for a canceled flag.

Another option was to create a
"Process Cancelation Token" class,
similar to what .net 4 Tasks use, then
that token be passed to those classes.

I am not familiar with this, but if it is basically an object with a bool property flag, and that you pass into each class, then this seems like the cleanest way to me. Then you could make an abstract base class that has a constructor that takes this in and sets it to a private member variable. Then your process loops can just check that for cancellation.
Obviously you will have to keep a reference to this object you have passed into your workers so that it's bool flag can be set on it from your UI.

生活了然无味 2024-09-12 05:17:18

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

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();
    }
}

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

Your nested types could accept a delegate (or expose an event) to check for a cancel condition. Your manager then supplies a delegate to the nested types that checks its own "shouldStop" boolean. This way, the only dependency is of the ManagerType on the NestedType, which you already had anyway.

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();
    }
}

You could abstract this behavior into an interface if you really wanted to.

interface IStoppable
{
    Predicate<bool> ShouldStop;
}

Also, rather than just check a boolean, you could have the "stop" mechanism be throwing an exception. In the manager's checkShouldStop method, it could simply throw an 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();
    }
}

I've used this technique before and find it very effective.

遇见了你 2024-09-12 05:17:18

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

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

在顶层捕获 OperationCanceledException

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

此方法还可以与 WinForms BackgroundWorker 组件配合使用。工作线程会自动捕获工作线程中抛出的异常,并将其封送回 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?
    }
}

Litter your code with statements like this wherever it is most sensible to check the stop flag:

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

Catch OperationCanceledException right at the top level.

There is no real performance penalty for this because (a) it won't happen very often, and (b) when it does happen, it only happens once.

This method also works well in conjunction with a WinForms BackgroundWorker component. The worker will automatically catch a thrown exception in the worker thread and marshal it back to the UI thread. You just have to check the type of the e.Error property, e.g.:

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?
    }
}
一瞬间的火花 2024-09-12 05:17:18

您可以通过使用命令模式将每个 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());
    }
}

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

You can flatten your call stack by turning each DoWork() call into a command using the Command pattern. At the top level, you maintain a queue of commands to perform (or a stack, depending on how your commands interact with each other). "Calling" a function is translated to enqueuing a new command onto the queue. Then, between processing each command, you can check whether or not to cancel. Like:

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());
    }
}

Basically, by turning the low-level callstack into a data structure you control, you have the ability to check stuff between each "call", pause, resume, cancel, etc..

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文