views:

98

answers:

3
+1  Q: 

stopping dll loop

I have a multi-thread C# application that uses some recursive functions in a dll. The problem that I have is how to cleanly stop the recursive functions.

The recursive functions are used to traverse our SCADA system's hierarchical 'SCADA Object' data. Traversing the data takes a long time (10s of minutes) depending on the size of our system and what we need to do with the data.

When I start the work I create a background thread so the GUI stays responsive. Then the background worker handles the calling of the recursive function in the dll.

I can send a cancel request to the background worker using CancelAsync but the background worker can't check the CancellationPending flag because it is blocked waiting of the dll's recursive function to finish.

Typically there is only 1 recursive function active at a time but there are dozens of recursive functions that are used at various times by different background workers.

As a quick (and really shameful) hack I added a global 'CodeEnabled' flag to the dll. So when the GUI does the CancelAsync it also sets the 'CodeEnabled' flag to false. (I know I need some of those bad code offsets). Then the dll's recursive loop checks the 'CodeEnabled' flag and returns to the background worker which is finally able to stop.

I don't want to move the recursive logic to the background worker thread because I need it in other places (e.g. other background workers).

What other approach should be used for this type of problem?

+2  A: 

It depends on the design, really. Much recursion can be replaced with (for example) a local stack (Stack<>) or queue (Queue<>), in which case a cancel flag can be held locally without too much pain. Another option is to use some kind of progress event that allows subscribers to set a cancel flag. A third option is to pass some kind of context class into the function(s), with a (volatile or synchronized) flag that can be set.

In any of these cases you should have relatively easy access to a cancel flag to exit the recursion.

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

with (in your function that accepts the context):

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

Another interesting option is to use iterator blocks for your long function rather than regular code; then your calling code can simply stop iterating when it has had enough.

Marc Gravell
Your right about the recursion being replaced by a stack or a queue. I guess the problem is just stopping any loop. The recursive functions is code that I inherited. A lot of the new loop methods I have written are actual iterator blocks. I found those to be much more useful. It keeps the methods very short (time wise) and the calling method (background worker in this case) still can shut down the loop naturally.I was reluctant to rewrite the code. I will just break down and do the work. I think the iterator block in my case is a better choice.Thanks.
rthompson
+1  A: 

Well, it seems to me that you need to propagate the "stop now" state down the recursive calls. You could have some sort of cancellation token which you pass down the recursive calls, and also keep hold of in the UI thread. Something as simple as this:

public class CancellationToken
{
    private volatile bool cancelled;

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

(I'm getting increasingly wary of volatility and lock-free coding; I would be tempted to use a lock here instead of a volatile variable, but I've kept it here for the sake of simplicity.)

So you'd create the cancellation token, pass it in, and then at the start of each recursive method call you'd have:

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

Then you'd just call Cancel() in the UI thread. Basically it's a just a way of sharing the state of "should this task continue".

The choice of whether to propagate a dummy return value back or throw an exception is an interesting one. In some ways this isn't exceptional - you must be partially expecting it, or you wouldn't pass the cancellation token in the first place - but at the same time exceptions have the behaviour you want in terms of unwinding the stack to somewhere that can recognise the cancellation easily.

Jon Skeet
Thanks Jon. I'm going to change my code to use iterator blocks (see Marc's answer). If I didn't change my code I think I would switch to your approach. In the end the dll function has taken over control when it is looping. There is no elegant way to stop the loop from the calling method, in this case the background worker. In the end the GUI has to send the stop to both the background worker and the dll function. That is where I have my concerns. The GUI knows too much. It should only know about the background worker and not be concerned with the dll. Thanks again.
rthompson
A: 

I like the previous answers, but here's another.

I think you're asking how to have different cancel flag for different threads.

Assuming that the threads which you might want to cancel each have some kind of ThreadId then, instead of having a single global 'CodeEnabled' flag, you could have a global thread-safe dictionary of flags, where the TheadId values are used as the dictionary's keys.

A thread would then query the dictionary to see whether its flag has been set.

ChrisW
You might as well use a thread-static variable in that case...
Jon Skeet
@Jon Although you want to read the flag from the thread to be cancelled, you want to be able to set the flag from a different thread (which I think the other thread can't do if the flag is thread-static).
ChrisW