How to exit loop on button press?

  • Thread starter Thread starter Ivan
  • Start date Start date
I

Ivan

I have process that can run for long time. User needs button to abort it.

I'm looking for elegant way to do that. Right now I can only think of shared
boolean flag that I will set on button_click and then check this flag while
looping.

Is there any "nicer" way to do this?

Thank you,
Ivan
 
Well.. I pretty much like to stay away from using form-level flags. Maybe
it's my personal "bug", but I'm looking at cleaner ways. To me it's like
populating global variables and then calling function to use them. Instead
of calling function and passing variables.

In my case I don't use threads, so boolean flag will do no problem. I though
there maybe better way to do that.
 
Well.. I pretty much like to stay away from using form-level flags. Maybe
it's my personal "bug", but I'm looking at cleaner ways. To me it's like
populating global variables and then calling function to use them. Instead
of calling function and passing variables.

Sounds like at some point in the past you've read or been told that "global
variables are BAD BAD BAD!!" I say if it makes sense to use them, use them.
Don't worry about whether some Computer Science professor might turn his
nose up at your code. In real-world programming you should do what works
best. Jumping through hoops to make sure your code won't be criticized by
some purist (including yourself!) is a complete waste of time and effort in
my opinion.

The use of a language feature is never a bad thing; it is the MISuse of a
feature that is bad.
 
You must use threads, if you expect to be able to respond to the UI at the
same time you are doing some lengthy processing.

Pete

Application.DoEvents()
will allow button_OnClick to execute...
 
Sounds like at some point in the past you've read or been told that
"global variables are BAD BAD BAD!!" I say if it makes sense to use them,
use them.

Not really. I program for living long enough and sometimes I get tired of my
own ways to do things and I like to know better ways. This kind of thing is
one of those..

Looks like boolean variable in order. I wonder what that professor will
sugest to do :)
 
And that's absolutely the wrong way to handle that. Your implementation
is flawed if it relies on Application.DoEvents().

Do yourself a favor: go ahead and implement a threaded solution. Using
DoEvents(), you create potential problems introducing reentrancy into code
that's not designed to be reentrant, and you turn the event-processing
model for .NET upside down.

Write your code the right way, and in the long run you will find it much
easier to maintain and have fewer and easier-to-find bugs.

Pete

See :) This is what I was asking about.

I agree that will be correct way to handle stuff. Now I just need to explain
why "Cancel" button is so expensive to do :)
 
Boo-hoo.

So you've saved that code that's not designed for re-entrancy from
re-entrancy and exposed it to simultaneous execution instead? I think it
will fare even worse.

Nevermind that .NET itself starts up modal message processing loops in
plenty of places.
See :) This is what I was asking about.

I agree that will be correct way to handle stuff. Now I just need to
explain why "Cancel" button is so expensive to do :)

It's not so complicated... Just use BackgroundWorker and be done with it.
Well, mostly. You still need to make sure you don't allow other operations
to run at the same time as the BackgroundWorker task that might interfere
with it. In fact pretty much the same issue as with using DoEvents.
 
In fact pretty much the same issue as with using DoEvents.

Thats what I feel... I did use multiple threads few times but I had very
good reason (TCP listener while user using UI)

But this seems to be overkill for simple loop and exit. Why BackGroundWorker
will be any better then DoEvents then?
 
Ivan said:
Not really.

Yes really, Jeff is 100% correct on this. While it is generally a good idea
to minimise global or module level variable use it is just as bad to avoid
them when they are needed. I can't really imagine the code you'd write to
avoid the variable in this case but I can't imagine it would be simpler than
a boolean variable.

BTW, I generally do this with a module level variabled call "running". At
the start of the long process I set it to true and at the end (in a finally
block) set it to false. In a cancel button click I set it to false and then
check it inside the long process and exit if it is false. This way I can
also use it in events such as OnClosing to see if the process is still
running. It makes perfect sense to have a module level variable to do this
as it does apply to the entire form.

Michael
 
Peter Duniho said:
And that's absolutely the wrong way to handle that. Your implementation
is flawed if it relies on Application.DoEvents().

Do yourself a favor: go ahead and implement a threaded solution. Using
DoEvents(), you create potential problems introducing reentrancy into code
that's not designed to be reentrant, and you turn the event-processing
model for .NET upside down.

Surely the potential for problems is ten fold when using a thread? The
renentracy problem still exists but surely is much worse with threads
because they can all execute together (with doevents if you kick off another
function at least that new function completes before anything else
executes). Don't get me wrong, I think the thread is probably the better
solution, I'm just not sure I agree with your reasoning. The reason I can
see that the thread is the better solution is that is makes sure the app is
responsive 100% of the time. With a complicated process it can be difficult
to get DoEvents in all the right places and sometimes there are longer
delays than would be expected which results in a jittery app. I guess this
would be most evident if you tried to move a window around the screen.

Michael
 
Yes really, Jeff is 100% correct on this.

Actually, I took his "not really" as meaning, "No, I don't believe that
because someone told me that; I believe it because I arrived at that
conclusion myself," based on the rest of his comment. He went on to say that
he was going to use the form-level Boolean....
 
Peter Duniho said:
"Tenfold", based on what data?

Issues such as updating the GUI from an alternate thread, race conditions,
dead locks etc. All the normal threading issues. This is not a reason to
avoid threads but just something to consider. I would suggest that someone
should either understand threading very well before using it or use doevents
if they cannot invest that time at the moment.
There is no reentrancy at all if the work is done on a thread. All of the
GUI is still run on a single thread, and only one GUI event handler/method
can be executing at a time.

The same issues exist with a thread and doevents, except they are much more
potentially dangerous. If you've got some sort of button that fires off this
process that takes a long time then that button can be clicked twice in
either case and you need to check for this in either case. The user could
potentially double click the button.
Even if you have multiple tasks (and in an application where that's not
wanted, it's trivial to prevent), the tasks will either be interacting
with their own parts of the GUI, or if for some reason they share a common
part of the GUI (terrible design, but I suppose it could happen) the
message queue will serialize that interaction.

That's true but I don't see how it's relevant. Re-entrancy is still possible
with either method. Are you saying it's not possible with threads? :-)
This is certainly another good reason to put things in a thread, you are
correct. But it's not the only one, and this reason you mention is more
one of usability, rather than code correctness. That is, I personally
would worry more about issues that can cause the code the fail, rather
than those issues that simply cause irritation to the user (even as I do
worry about the latter as well).

Besides correctness of code this is the only reason I can see. From the
user's point of view app responsiveness is really the only difference.

Michael
 
Peter Duniho said:
I don't see how, even if those were salient issues, you derive a numerical
comparison from those general issues.

Ten fold was just a phrase :-) You're taking things a little too seriously.
I would suggest that a person who doesn't have the time to invest in
learning how to use BackgroundWorker in its most basic usage, they also
don't have the time to invest in ensuring that code using DoEvents() is in
fact immune from the problems related to that.

DoEvents() is not a panacea.

Yes but with DoEvents other code only runs on the DoEvents command and the
DoEvents call does not complete until this code has run.
See my reply to Ben. The same issues definitely do _not_ exist. Each has
their own issues, but different ones.


Impossible as long as the code correctly disables the button after the
first click and not reenabled until the BackgroundWorker's
RunWorkerCompleted event is raised.

And the same thing is true whether the processing is implemented using
threading or DoEvents(). This is a complete red herring, totally
irrelevant.

I thought you said these things were completely different. Now you say they
are the same. :-)
Re-entrancy is possible in either case, but out of the two, only
DoEvents() results in re-entrancy with no explicit implementation on the
programmer's part. That is, in fact, exactly my point. If you wind up
with re-entrant code with threads, it's because you intentionally called
something recursively. But with DoEvents(), it can happen even without
any intentional act on the part of the programmer.

And in my book, code that can do things unintentionally is less desirable
(i.e. "worse") than code that only does things intentionally.

You're really stretching there but to be honest I can't be bothered any
longer. I am not saying that DoEvents is the better method, obviously it is
not. I am just saying your reasoning is somewhat lacking.
Okay, so you agree: code correctness _and_ UI correctness are both issues.

I never said otherwise. Have fun.

Michael
 
The BackgroundWorker handler can easily be written to act in isolation,
opening specific interactions with the rest of the code as necessary, in
predictable ways. The re-entrancy that exists when using DoEvents() is
pervasive, with _all_ of the code opened to the rest by default, limited
only when one explicitly does so.

Not at all. The only code that can be re-entered is code on the call stack
to DoEvents. In fact, as long as the process restores all invariants before
calling DoEvents and understands that other entire operations may complete
during the DoEvents call, it will work. This is far simpler to achieve than
the "other operations can happen at any time and you can see intermediate
results and they see your intermediate results" which is true of
multithreading. In essence, the "act in isolation" concept which works on a
separate thread will also work perfectly well with reentrancy.
 
Peter Duniho said:
[...]
If you've got some sort of button that fires off this
process that takes a long time then that button can be clicked twice in
either case and you need to check for this in either case. The user
could
potentially double click the button.

Impossible as long as the code correctly disables the button after the
first click and not reenabled until the BackgroundWorker's
RunWorkerCompleted event is raised.

And the same thing is true whether the processing is implemented using
threading or DoEvents(). This is a complete red herring, totally
irrelevant.

I thought you said these things were completely different. Now you say
they
are the same. :-)

You're being obtuse. I believe you're doing so intentionally.

What I said was that the potential _dangerous_ issues are different. You

And I disagree. Most of the trouble can be summarized as "the object can be
accessed when another operation is in-progress and the state is
inconsistent". This is true for both re-entrancy and multi-threading, and
it is worse for multithreading (with re-entrance, if an operation is in
progress, you know it is stopped at one of a finite number of interruption
points, but with multithreading, the other operation can be at any point and
may not even be stopped). OTOH, using a lock to solve the problem is easier
with threading, since depending on whether the lock is re-entrant or not,
locks in re-entrance will either deadlock the task or do nothing at all.
But this is somewhat a case of "when all you have is a hammer, everything
looks like a nail". Threading and re-entrancy present similar problems but
have to be addressed with different tools. In the end, though, both have to
guarantee atomicity of operations that restore consistency of the data
structures, and this is trivial to do in the re-entrant case, and commonly
uses locks in the threaded case.
 
Back
Top