我有一个类是“经理”类。其中一个功能是发出应该关闭该类的长时间运行过程的信号。它通过在类中设置一个名为“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:
感谢目前为止的回复。似乎对使用的方法没有真正的共识,每个人都有不同的意见。看起来这应该是一种设计模式......
答案 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());
}
}
基本上,通过将低级调用堆栈转换为您控制的数据结构,您可以在每次“调用”,暂停,恢复,取消等之间检查内容。