我有一个多线程C#应用程序,它在dll中使用了一些递归函数。我遇到的问题是如何干净地停止递归函数。
递归函数用于遍历我们的SCADA系统的分层“SCADA对象”数据。遍历数据需要很长时间(10分钟),具体取决于我们系统的大小以及我们需要对数据执行的操作。
当我开始工作时,我创建了一个后台线程,以便GUI保持响应。然后后台工作者处理在dll中调用递归函数。
我可以使用CancelAsync向后台工作人员发送取消请求,但后台工作人员无法检查CancellationPending标志,因为它被阻止等待dll的递归函数完成。
通常,一次只有一个递归函数处于活动状态,但有不同背景工作者在不同时间使用的几十个递归函数。
作为一个快速(并且非常可耻)的黑客,我在dll中添加了一个全局“CodeEnabled”标志。因此,当GUI执行CancelAsync时,它还将'CodeEnabled'标志设置为false。 (我知道我需要一些错误的代码偏移)。然后dll的递归循环检查'CodeEnabled'标志并返回最终能够停止的后台工作程序。
我不想将递归逻辑移动到后台工作线程,因为我需要在其他地方(例如其他后台工作者)。
这类问题应采用哪种其他方法?
答案 0 :(得分:2)
这取决于设计,真的。很多递归可以替换为(例如)本地堆栈(Stack<>
)或队列(Queue<>
),在这种情况下,取消标志可以在本地保存而不会太痛苦。另一种选择是使用允许订户设置取消标志的某种进度事件。第三种选择是将某种上下文类传递给函数,并使用可设置的(volatile或synchronized)标志。
在任何这些情况下,您都应该相对容易地访问取消标志以退出递归。
FooContext ctx = new FooFontext();
BeginSomeRecursiveFunction(ctx);
...
ctx.Cancel = true; // or ctx.Cancel(), whatever
with(在你的函数中接受上下文):
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而不是常规代码;然后你的调用代码可以在它已经足够时停止迭代。
答案 1 :(得分: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
}
然后你只需在UI线程中调用Cancel()
。基本上它只是一种分享“应该继续这个任务”状态的方式。
是否传播虚拟返回值或抛出异常的选择是一个有趣的选择。在某些方面,这不是例外 - 你必须部分期待它,或者你不会首先传递取消令牌 - 但同时异常具有你想要的行为,将堆栈展开到某个地方可以轻松识别取消。
答案 2 :(得分:0)
我喜欢以前的答案,但这是另一个答案。
我想你问的是如何为不同的线程设置不同的取消标志。
假设您可能想要取消的线程各自具有某种ThreadId,那么您可以使用全局线程安全的标志字典,而不是使用单个全局“CodeEnabled”标志,其中使用TheadId值作为字典的关键。
然后,线程将查询字典以查看其标志是否已设置。