Long Process

  • Thread starter Thread starter John Wright
  • Start date Start date
J

John Wright

I have a long process I run that needs to be complete before the user can continue. I can change the cursor to an hourglass, but I want to update the Status Strip on the bottom during the process. I set the statusstrip label to "Downloading...", but it will not show up on the form. I need to display this message before it starts the process. I can put a thread.sleep for 1 second after I do the initial process, but that seems sloppy to me. Any suggestions? Here is my code launched from a button click:


'update the label--This does not show because the process starts before the UI updates
slDBList.Text = "Retrieving Databases..."TryMe.Cursor = Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case of an error boolServersLoaded = FalseboolDBLoaded = False'clear the servers list cbServers.DataSource = Nothing'clear the databases list dbDatabases.DataSource = NothingIf chkSort.Checked = True ThenlstServers = GetSqlServers(True)ElselstServers = GetSqlServers(False)End IfcbServers.DataSource = lstServers'set to true to enabled the selected index change on the dropdown boolServersLoaded = TrueslDBList.Text = ""Catch ex As ExceptionMessageBox.Show("Error loading server list. Error: " & ex.ToString, "Data Error", MessageBoxButtons.OK, MessageBoxIcon.Error)boolServersLoaded = FalseslDBList.Text = ""FinallyMe.Cursor = Cursors.ArrowEnd Try
 
I have a long process I run that needs to be complete before the user can continue.  I can change the cursor to an hourglass, but I want to update the Status Strip on the bottom during the process.  I set the statusstrip label to "Downloading...", but it will not show up on the form.  I need to display this message before it starts the process.  I can put a thread.sleep for 1 second after I do the initial process, but that seems sloppy to me. Any suggestions?  Here is my code launched from a button click:

'update the label--This does not show because the process starts before the UI updates
slDBList.Text = "Retrieving Databases..."TryMe.Cursor = Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case of an error boolServersLoaded = FalseboolDBLoaded = False'clear the servers list cbServers.DataSource = Nothing'clear the databases list dbDatabases.DataSource =NothingIf chkSort.Checked = True ThenlstServers = GetSqlServers(True)ElselstServers = GetSqlServers(False)End IfcbServers.DataSource = lstServers'set to true to enabled the selected index change on the dropdown boolServersLoaded = TrueslDBList.Text = ""Catch ex As ExceptionMessageBox.Show("Error loading server list. Error: " & ex.ToString, "Data Error", MessageBoxButtons.OK, MessageBoxIcon.Error)boolServersLoaded = FalseslDBList.Text =""FinallyMe.Cursor = Cursors.ArrowEnd Try

Hi,
Could you put the code above into BackgroundWorker's Do_Work event sub
and run it using RunWorkerAsync method?
And just set statusStrip text in button_click event.

Like this:
Private Sub Button1_Click (ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click

StatusStrip1.text = "Downloading..."
BackgroundWorker1.RunWorketAsync()

End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object,
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles
Backgroundworker1.DoWork

' Here is the code that you sent in your original post

End Sub


...should work fine.

Thanks,

Onur Güzel
 
This is a multi-part message in MIME format.

------=_NextPart_000_0354_01C8BB3B.79B811B0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

I have a long process I run that needs to be complete before the user =
can continue. I can change the cursor to an hourglass, but I want to =
update the Status Strip on the bottom during the process. I set the =
statusstrip label to "Downloading...", but it will not show up on the =
form. I need to display this message before it starts the process. I =
can put a thread.sleep for 1 second after I do the initial process, but =
that seems sloppy to me. Any suggestions? Here is my code launched =
from a button click:


'update the label--This does not show because the process starts before =
the UI updates
slDBList.Text =3D "Retrieving Databases..."TryMe.Cursor =3D =
Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case =
of an error boolServersLoaded =3D FalseboolDBLoaded =3D False'clear the =
servers list cbServers.DataSource =3D Nothing'clear the databases list =
dbDatabases.DataSource =3D NothingIf chkSort.Checked =3D True =
ThenlstServers =3D GetSqlServers(True)ElselstServers =3D =
GetSqlServers(False)End IfcbServers.DataSource =3D lstServers'set to =
true to enabled the selected index change on the dropdown =
boolServersLoaded =3D TrueslDBList.Text =3D ""Catch ex As =
ExceptionMessageBox.Show("Error loading server list. Error: " & =
ex.ToString, "Data Error", MessageBoxButtons.OK, =
MessageBoxIcon.Error)boolServersLoaded =3D FalseslDBList.Text =3D =
""FinallyMe.Cursor =3D Cursors.ArrowEnd Try
------=_NextPart_000_0354_01C8BB3B.79B811B0
Content-Type: text/html;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1">
<META content=3D"MSHTML 6.00.2900.3314" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY>
<DIV><FONT face=3DArial size=3D2>I have a long process I run that needs =
to be=20
complete before the user can continue.&nbsp; I can change the cursor to =
an=20
hourglass, but I want to update the Status Strip on the bottom during =
the=20
process.&nbsp; I set the statusstrip label to "Downloading...", but it =
will not=20
show up on the form.&nbsp; I need to display this message before it =
starts the=20
process.&nbsp; I can put a thread.sleep for 1 second after I do the =
initial=20
process, but that seems sloppy to me.&nbsp; Any suggestions?&nbsp; Here =
is my=20
code launched from a button click:</FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>'update the label--This does not show =
because the=20
process starts before the UI updates</FONT></DIV><PRE>slDBList.Text =3D =
"Retrieving Databases..."</PRE><PRE>Try</PRE><PRE>Me.Cursor =3D =
Cursors.WaitCursor</PRE><PRE>Dim lstServers As List(Of =
String)</PRE><PRE>'set to false in case of an error =
</PRE><PRE>boolServersLoaded =3D False</PRE><PRE>boolDBLoaded =3D =
False</PRE><PRE>'clear the servers list </PRE><PRE>cbServers.DataSource =
=3D Nothing</PRE><PRE>'clear the databases list =
</PRE><PRE>dbDatabases.DataSource =3D Nothing</PRE><PRE>If =
chkSort.Checked =3D True Then</PRE><PRE>lstServers =3D =
GetSqlServers(True)</PRE><PRE>Else</PRE><PRE>lstServers =3D =
GetSqlServers(False)</PRE><PRE>End If</PRE><PRE>cbServers.DataSource =3D =
lstServers</PRE><PRE>'set to true to enabled the selected index change =
on the dropdown </PRE><PRE>boolServersLoaded =3D =
True</PRE><PRE>slDBList.Text =3D ""</PRE><PRE>Catch ex As =
Exception</PRE><PRE>MessageBox.Show("Error loading server list. Error: " =
&amp; ex.ToString, "Data Error", MessageBoxButtons.OK, =
MessageBoxIcon.Error)</PRE><PRE>boolServersLoaded =3D =
False</PRE><PRE>slDBList.Text =3D =
""</PRE><PRE>Finally</PRE><PRE>Me.Cursor =3D Cursors.Arrow</PRE><PRE>End =
Try</PRE></BODY></HTML>

------=_NextPart_000_0354_01C8BB3B.79B811B0--

Take a look at the BackgroundWorker component.
 
Hi,
Could you put the code above into BackgroundWorker's Do_Work event sub
and run it using RunWorkerAsync method?
And just set statusStrip text in button_click event.

Like this:
Private Sub Button1_Click (ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click

StatusStrip1.text = "Downloading..."
BackgroundWorker1.RunWorketAsync()

Sorry for the last line's typo,
BackgroundWorker1.RunWorkerAsync() is the correct one.

Thanks,

Onur Güzel
 
You also can try the controls/form Refresh method, which forces it to
be redraw.

Thiago

If the form is on top of all other windows, yes, this can be used to
make sure a message is updated *before* the process is started,
but once the process is running, the UI thread is blocked until
the task has finished.

If another form (from another application, e.g.) is moved across
the form while the task is running, you might be looking at a big,
grey blob instead of a form telling you what it is working on.

Only when the task is done and the UI thread is no longer blocked
does the form get a chance to repaint itself again.

Regards,

Joergen Bech
 
How about Application.DoEvents ? This might help for processing pending thread.

Mayur H Chauhan
I have a long process I run that needs to be complete before the user can continue. I can change the cursor to an hourglass, but I want to update the Status Strip on the bottom during the process. I set the statusstrip label to "Downloading...", but it will not show up on the form. I need to display this message before it starts the process. I can put a thread.sleep for 1 second after I do the initial process, but that seems sloppy to me. Any suggestions? Here is my code launched from a button click:


'update the label--This does not show because the process starts before the UI updates
slDBList.Text = "Retrieving Databases..."TryMe.Cursor = Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case of an error boolServersLoaded = FalseboolDBLoaded = False'clear the servers list cbServers.DataSource = Nothing'clear the databases list dbDatabases.DataSource = NothingIf chkSort.Checked = True ThenlstServers = GetSqlServers(True)ElselstServers = GetSqlServers(False)End IfcbServers.DataSource = lstServers'set to true to enabled the selected index change on the dropdown boolServersLoaded = TrueslDBList.Text = ""Catch ex As ExceptionMessageBox.Show("Error loading server list. Error: " & ex.ToString, "Data Error", MessageBoxButtons.OK, MessageBoxIcon.Error)boolServersLoaded = FalseslDBList.Text = ""FinallyMe.Cursor = Cursors.ArrowEnd Try
 
When I do this I get a cross thread error. Any ideas?

John


I have a long process I run that needs to be complete before the user can
continue. I can change the cursor to an hourglass, but I want to update
the Status Strip on the bottom during the process. I set the statusstrip
label to "Downloading...", but it will not show up on the form. I need to
display this message before it starts the process. I can put a
thread.sleep for 1 second after I do the initial process, but that seems
sloppy to me. Any suggestions? Here is my code launched from a button
click:

'update the label--This does not show because the process starts before
the UI updates
slDBList.Text = "Retrieving Databases..."TryMe.Cursor =
Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case
of an error boolServersLoaded = FalseboolDBLoaded = False'clear the
servers list cbServers.DataSource = Nothing'clear the databases list
dbDatabases.DataSource = NothingIf chkSort.Checked = True ThenlstServers =
GetSqlServers(True)ElselstServers = GetSqlServers(False)End
IfcbServers.DataSource = lstServers'set to true to enabled the selected
index change on the dropdown boolServersLoaded = TrueslDBList.Text =
""Catch ex As ExceptionMessageBox.Show("Error loading server list. Error:
" & ex.ToString, "Data Error", MessageBoxButtons.OK,
MessageBoxIcon.Error)boolServersLoaded = FalseslDBList.Text =
""FinallyMe.Cursor = Cursors.ArrowEnd Try

Hi,
Could you put the code above into BackgroundWorker's Do_Work event sub
and run it using RunWorkerAsync method?
And just set statusStrip text in button_click event.

Like this:
Private Sub Button1_Click (ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click

StatusStrip1.text = "Downloading..."
BackgroundWorker1.RunWorketAsync()

End Sub

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object,
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles
Backgroundworker1.DoWork

' Here is the code that you sent in your original post

End Sub


....should work fine.

Thanks,

Onur Güzel
 
When I do this I get a cross thread error. Any ideas?

John

John,

You can't access any of the forms controls in the actual DoWork event.
You need to handle any updates during your work by calling the
ReportProgress method of the backgroundworker. This will cause the
ProgressChanged event to be raised (assuming you have set the
WorkerReportsProgress property to true). In that event it is safe to
access your forms controls.
 
John,

You can't access any of the forms controls in the actual DoWork event.
You need to handle any updates during your work by calling the
ReportProgress method of the backgroundworker. This will cause the
ProgressChanged event to be raised (assuming you have set the
WorkerReportsProgress property to true). In that event it is safe to
access your forms controls.


Hi,
Tom is right, and your form elements are getting updated and accessed
during BackgroundWorker's DoWork event, that's why the reason of error
i guess.

Thanks,

Onur Güzel
 
Joergen,

Be aware that you never should do what you advice as it is a critical
process that has to be completed, and the user can end that in the middle
without knowing what is the effect.

Think by instance in a paying process by banks, were steps have to be done,
I would never be the one who would give an advice as yours.

Just my idea,

Cor
 
John,

Mostly it helps, when you set it in a seperate method.

Cor

"John Wright" <[email protected]> schreef in bericht I have a long process I run that needs to be complete before the user can continue. I can change the cursor to an hourglass, but I want to update the Status Strip on the bottom during the process. I set the statusstrip label to "Downloading...", but it will not show up on the form. I need to display this message before it starts the process. I can put a thread.sleep for 1 second after I do the initial process, but that seems sloppy to me. Any suggestions? Here is my code launched from a button click:


'update the label--This does not show because the process starts before the UI updates
slDBList.Text = "Retrieving Databases..."TryMe.Cursor = Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case of an error boolServersLoaded = FalseboolDBLoaded = False'clear the servers list cbServers.DataSource = Nothing'clear the databases list dbDatabases.DataSource = NothingIf chkSort.Checked = True ThenlstServers = GetSqlServers(True)ElselstServers = GetSqlServers(False)End IfcbServers.DataSource = lstServers'set to true to enabled the selected index change on the dropdown boolServersLoaded = TrueslDBList.Text = ""Catch ex As ExceptionMessageBox.Show("Error loading server list. Error: " & ex.ToString, "Data Error", MessageBoxButtons.OK, MessageBoxIcon.Error)boolServersLoaded = FalseslDBList.Text = ""FinallyMe.Cursor = Cursors.ArrowEnd Try
 
Sorry,

I had not yet readed Mayurs answer,

That is most probably your answer,
As that does not work, combine his and mine.

Cor
"Cor Ligthert[MVP]" <[email protected]> schreef in bericht John,

Mostly it helps, when you set it in a seperate method.

Cor

"John Wright" <[email protected]> schreef in bericht I have a long process I run that needs to be complete before the user can continue. I can change the cursor to an hourglass, but I want to update the Status Strip on the bottom during the process. I set the statusstrip label to "Downloading...", but it will not show up on the form. I need to display this message before it starts the process. I can put a thread.sleep for 1 second after I do the initial process, but that seems sloppy to me. Any suggestions? Here is my code launched from a button click:


'update the label--This does not show because the process starts before the UI updates
slDBList.Text = "Retrieving Databases..."TryMe.Cursor = Cursors.WaitCursorDim lstServers As List(Of String)'set to false in case of an error boolServersLoaded = FalseboolDBLoaded = False'clear the servers list cbServers.DataSource = Nothing'clear the databases list dbDatabases.DataSource = NothingIf chkSort.Checked = True ThenlstServers = GetSqlServers(True)ElselstServers = GetSqlServers(False)End IfcbServers.DataSource = lstServers'set to true to enabled the selected index change on the dropdown boolServersLoaded = TrueslDBList.Text = ""Catch ex As ExceptionMessageBox.Show("Error loading server list. Error: " & ex.ToString, "Data Error", MessageBoxButtons.OK, MessageBoxIcon.Error)boolServersLoaded = FalseslDBList.Text = ""FinallyMe.Cursor = Cursors.ArrowEnd Try
 
From what I understand, we were talking about a heavy, long-running
task with an unresponsive interface. In the case of a bank
transaction, we would be dealing with an optimistic or pessimistic
locking problem - not a threading one, as multithreading would be
entirely inappropriate in such a scenario.

The BackgroundWorker class is there for a reason and I was not
the only one suggesting it.

The class provides properties, events, and methods for controlling
how an asynchronous task could or should be aborted.

It is up to the coder to use these properly to ensure that the ACID
properties of the operation are maintained.

The form that initiated the process should not just kill it directly
at the user's request: The pattern is to send a signal to the task,
asking it to cancel itself. The long-running task should periodically
check this flag and either ignore it or do whatever is necessary to
back out of whatever it is doing.

I have used BackgroundWorker myself in a similar scenario and I
can assure you that I took steps to ensure that the only thing the
user could do was watch the progress or cancel the operation
before being able to do anything else.

As for DoEvents as an alternative, I would like to direct you and
the original poster to these articles:
http://www.codinghorror.com/blog/archives/000159.html
http://www.codinghorror.com/blog/archives/000370.html

Bear in mind that the first article before BackgroundWorker came
onto the scene and made life much easier in this regard.
The important thing is the comments section, which contains
information and anecdotes that could be said to be of more
value than the articles themselves.

For my own part, I have seen applications where DoEvents
statements were littered all over the place to improve responsiveness.
Those apps were a mess of reentrancy problems, which only goes
to show that if you do not know what you are doing in the first place,
you should not touch DoEvents, let alone BackgroundWorker.
Though DoEvents code is "easier" to debug than BackgroundWorker
ditto, the issues are the same.

Better to have an unresponsive application that works than a
responsive one that doesn't.

Getting back to BackgroundWorker: The code example in the
online documentation should be enough to getting started with.

Regards,

Joergen Bech
 
Cor Ligthert said:
Joergen,

Be aware that you never should do what you advice as it is a
critical process that has to be completed, and the user can end that
in the middle without knowing what is the effect.

Think by instance in a paying process by banks, were steps have to
be done, I would never be the one who would give an advice as yours.

Just my idea,

Cor

Like Joergen, I don't understand this plea. Using another thread to
avoid the UI thread being blocked is the (only) right action, IMO. But
maybe you meant that the UI thread also have to be in an appropriate
state depending on the whole situation, where I would agree.


Armin
 
Mayur H Chauhan said:
How about Application.DoEvents ? This might help for processing
pending thread.

Doevents should not be used anymore. It was valid with previous VB
versions where multi threading was (appart from appartment threading)
almost impossible. Doevents creates more problems than necessary. First,
the classification of UI tasks and independent other tasks would have to
be removed. Not every task has an abstract and regular event/callback
mechanism that can be used to update the UI, especially when talking
about long running database activities. In addition, you'd have
to be aware of code reentrance. Moreover, why create new code to process
messages even if there is already a message loop that is made for it? My
experience is that almost every time when I was using Doevents, 5 LOC
later I'd wished to go the "clean" way and had used a separate thread
instead.


Armin
 
Doevents should not be used anymore. It was valid with previous VB
versions where multi threading was (appart from appartment threading)
almost impossible. Doevents creates more problems than necessary. First,
the classification of UI tasks and independent other tasks would have to
be removed. Not every task has an abstract and regular event/callback
mechanism that can be used to update the UI, especially when talking
about long running database activities. In addition, you'd have
to be aware of code reentrance. Moreover, why create new code to process
messages even if there is already a message loop that is made for it? My
experience is that almost every time when I was using Doevents, 5 LOC
later I'd wished to go the "clean" way and had used a separate thread
instead.


Armin

I would like to add that as a general tool for UI responsiveness,
DoEvents is not all it is cracked up to be, as opposed to
BackgroundWorker:

If we are just running a homogenous loop, sure, we can call DoEvents
every nn iterations, giving the interface a chance to refresh itself,
but if the long-running tasks consists of a number of different
subtasks, we cannot depend on all tasks to be able to call DoEvents
at the same, regular intervals. Suppose the task is waiting for a
response or timeout from a database server?

Using BackgroundWorker (or threads), on the other hand, the
scenario is like this:

1. User initiates task.
2. UI displays "Processing..."
3. User clicks Cancel button.
4. UI displays "Cancelling..."
5. Message is sent to thread, asking it to stop whatever it is doing
6. When the task, which is dutifully monitoring the Cancel flag when
it can, has finished, it informs the thread about its status.
7. UI displays "Cancelled" or "Completed".

So in the case of subtasks where it is not possible to call DoEvents
for a few seconds, the DoEvents approach would still result in a
- sometimes - unresponsive interface, whereas the BackgroundWorker
approach would allow the user to click the Cancel button immediately
and then see a "Cancelling..." message for a couple of seconds until
the task had a chance to respond.

Regards,

Joergen Bech
 
but if the long-running tasks consists of a number of different
subtasks, we cannot depend on all tasks to be able to call DoEvents
at the same, regular intervals.

Agree. That's what I meant with "Not every task has an abstract and
regular event/callback mechanism that can be used to update the UI,
especially when talking about long running database activities."

(But I see it as your agreement supplemented by the BGW)


Armin
 
Agree. That's what I meant with "Not every task has an abstract and
regular event/callback mechanism that can be used to update the UI,
especially when talking about long running database activities."

So you did. And so much more nicely put than my long-winded
explanation. Do you know the type of person who reads half a
post, and, blinded to the details, thinks he has an idea what
the whole thing contains, proceeds to write a follow-up?

Well, that was a good example, however much I usually try to avoid
being such a person.
(But I see it as your agreement supplemented by the BGW)

I'll pretend that additional information was my excuse for
posting it and saves some face.

Regards,

Joergen Bech
 
Back
Top