2010-01-15 222 views
1

我有一个多线程的C#应用​​程序,在DLL中使用一些递归函数。我的问题是如何干净地停止递归函数。停止DLL循环

递归函数用于遍历我们的SCADA系统的层次化'SCADA对象'数据。遍历数据需要很长时间(10分钟),具体取决于系统的大小以及我们需要处理的数据。

当我开始工作时,我创建了一个后台线程,以便GUI保持响应。然后后台工作人员处理dll中递归函数的调用。

我可以使用CancelAsync向后台工作人员发送取消请求,但后台工作人员无法检查CancellationPending标志,因为它被阻止等待dll的递归函数完成。

通常,一次只有一个递归函数处于活动状态,但有几十个递归函数会在不同时间由不同的后台工作人员使用。

作为一种快速(而且非常可耻)的破解,我在dll中添加了一个全局的'CodeEnabled'标志。因此,当GUI执行CancelAsync时,它也会将'CodeEnabled'标志设置为false。 (我知道我需要一些错误的代码偏移量)。然后dll的递归循环检查'CodeEnabled'标志并返回到最终能够停止的后台工作者。

我不想将递归逻辑移动到后台工作线程,因为我在其他地方需要它(例如其他后台工作者)。

这种类型的问题应该采用哪种方法?

回答

2

这取决于设计,真的。很多递归可以用(例如)本地堆栈(Stack<>)或队列(Queue<>)替换,在这种情况下,可以在本地保留取消标志,而不会造成太大的痛苦。另一种选择是使用某种进度事件,允许订户设置取消标志。第三种选择是将某种上下文类传递给函数,并且可以设置一个(易失性或同步)标志。

在任何这些情况下,您应该可以相对容易地访问取消标志以退出递归。

FooContext ctx = new FooFontext(); 
BeginSomeRecursiveFunction(ctx); 
... 
ctx.Cancel = true; // or ctx.Cancel(), whatever 

与(在你的函数接受的情况下):

if(ctx.Cancel) return; // or maybe throw something 
         // like an OperationCancelledException(); 
blah... 
CallMyself(ctx); // and further down the rabbit hole we go... 

另一个有趣的选择是使用iterator blocks for your long function而不是常规代码;那么你的调用代码可以在它已经足够时停止迭代。

+0

你的权利关于递归被堆栈或队列取代。我想这个问题只是停止任何循环。 递归函数是我继承的代码。我写的很多新的循环方法都是实际的迭代器模块。我发现那些更有用。它保持方法非常短(时间明智),调用方法(本例中的后台工作者)仍然可以自然地关闭循环。 我不愿意重写代码。我会分解并做这项工作。我认为在我的情况下,迭代器块是一个更好的选择。 谢谢。 – rthompson 2010-01-15 14:52:22

1

那么,在我看来,你需要在递归调用中传播“立即停止”状态。你可能有某种取消令牌,你沿着递归调用传递,并且还保留在UI线程中。事情如此简单:

public class CancellationToken 
{ 
    private volatile bool cancelled; 

    public bool IsCancelled { get { return cancelled; } } 
    public void Cancel() { cancelled = true; } 
} 

(我变得越来越警惕波动率和无锁编码的;我会被诱惑到这里使用的锁,而不是volatile变量的,但是我在这里保留了它为了简单起见。)

所以,你会创建取消标记,通过它,然后在每个递归方法的开始通话你必须:

if (token.IsCancelled) 
{ 
    return null; // Or some other dummy value, or throw an exception 
} 

然后你只需要调用Cancel()在UI线。基本上它只是一种分享“这个任务应该继续”状态的方式。

选择是传播一个虚拟返回值还是抛出一个异常是一个有趣的选择。在某些方面,这不是例外 - 你必须部分期待它,或者你不会首先传递取消标记 - 但是同时例外情况具有你希望将堆栈展开到某处的行为可以轻松识别取消。

+0

谢谢乔恩。我将更改我的代码以使用迭代器块(请参阅Marc的答案)。如果我没有改变我的代码,我想我会改用你的方法。最后,dll函数在循环时接管了控制权。没有优雅的方法来停止调用方法的循环,在这种情况下是后台工作者。最后,GUI必须将停止发送给后台工作人员和dll功能。这是我的担忧。 GUI知道太多。它应该只知道后台工作人员,而不是关心该DLL。再次感谢。 – rthompson 2010-01-15 15:03:09

0

我喜欢以前的答案,但这里是另一个。

我想你是问如何为不同的线程有不同的取消标志。

假设您可能想要取消的线程每个都有某种ThreadId,那么您可以拥有一个全局线程安全的标志字典,而不是具有单个全局'CodeEnabled'标志,其中TheadId值是用作字典的键。

然后,线程将查询字典以查看其标志是否已设置。

+0

你不妨在这种情况下使用一个线程静态变量...... – 2010-01-15 07:53:23

+0

@Jon虽然你想从被取消的线程中读取标志,但你希望能够设置来自不同线程的标志(我如果该标志是线程静态的,则认为其他线程无法执行)。 – ChrisW 2010-01-15 09:04:49