tags:

views:

1083

answers:

3

I have a simple C# foreach loop, how can I break out of the loop when a button is pressed? It is not in a backgroundWorker thread so I can't use backgroundWorker.CancellationPending.

+5  A: 

Create a boolean flag in the form. Attach an event handler to the buttons click event and set the flag to true in the event handler.

Check for the flag in the loop and if it's true, call "break". (A better option would be to use a while loop instead of a for loop with the check of the flag in the while condition)

Obviously the for loop will need to be on some form of background thread otherwise the GUI won't be responsive. If you don't know how to do this, check out either ThreadPool.QueueUserWorkItem or the BackgroundWorker. If you do use a BackgroundWorker you can use the inbuilt CancelAsync() method instead of coding your own cancel flag.

[Edit: As pointed out by others (below) and discussed in more depth here and here; you do need to either lock before accessing the bool or mark it as volatile]

[Don't forget to set the correct state of the flag before you start the loop.]

Simon P Stevens
Obviously the flag has to be volatile, or the access has to be synchronized.
EFraim
efraim, why would you need to synchronize access to a simple bool field? The user can't very well press the button that starts the loop AND the button that cancels it at the same time, can they?
Yar
@EFraim. I'm not sure about this. I don't think you need to lock it because you are only ever writing to it from one place (the button click event). The for loop is only reading it, so even if the read and write occurred at the same time, the worst case is that it would just have to perform 1 more loop iteration before it would stop. Can anyone confirm this?
Simon P Stevens
Because compiler can legitimately optimize away any such access, if it sees fit.
EFraim
BTW, before giving advice on multithreading issues I would at least check that you know what register optimization is
EFraim
@Simon: the worst case is actually that it will loop forever, because the compiler won't ever try to access the variable.
EFraim
Operations on built-in types are atomic, as stated in the ECMA. The volatile keyword is needed to ensure that there's a memory barrier so two concurrent threads always get the correct value and not the cached one.
arul
@arul: You are wrong. Atomicity has nothing to do with it. The compiler can assume it can cache variable value if it is not volatile. Why do I have to spend my time explaining such basic?!
EFraim
@yar : 1. This can happen. (on two CPUs) 2. Atomicity has nothing to do with it as I already stated.
EFraim
Even a boolean, when accessed from another thread, needs either `lock` (as a memory-barrier) or `volatile` to **guarantee** that it will be re-read; it is possible to show examples where it gets stuck in the registers otherwise: http://stackoverflow.com/questions/458173/can-a-c-thread-really-cache-a-value-and-ignore-changes-to-that-value-on-other-th/458193#458193
Marc Gravell
+4  A: 

Here is a not-so-good solution.

Call Application.DoEvents() in every iteration your loop. On Button-Click set a variable to True and in loop check that variable. If it is true, break otherwise continue.

As I said, this is not a very good solution. Application.DoEvents() can be very dangerous. The best option here to do is to have a background thread.

Aamir
I agree. This is a not-so-good solution. Don't use DoEvents. Put the for loop on a background thread properly using a background worker or ThreadPool.QueueUserWorkItem()
Simon P Stevens
+1 for something that actually works, even if it's a rather ugly solution that doesn't scale... You can add a counter to it and call DoEvents every n:th iteration to reduce the performance hit.
Guffa
A: 

You can use DoEvents, as Aamir suggested, as a quick fix.

For a more scalable and maintainable solution, you need to move the work to a separate thread. Then it's rather trivial to have a button click event set a flag to stop the loop. On the other hand, there is a bit more work to communicate the result back to the UI thread.

Guffa