Multi-Threaded UI

M

Michael Tumy

I have an application that could do some lengthy updates of a database upon
closing a form. So to keep the UI responsive, I'm pushing the updates off to
a worker thread. I seen numerous articles on using BeginInvoke on delgates
to keep you UI responsive. In these articles, they do a "WaitOne()" call on
the UI thread while the worker thread is processing the data.

This confuses me. How can the UI thread be responsive if it's blocked via
the WaitOne call? I've found that my UI does not redraw if I switch from
this app to another, then back while the update is in progress.

Do I have my code structured correctly? What I'm looking for is when the
"OK" button of the form is clicked, perform the update and then close the
form if the updated succeeded. Since this can take a long time, I need to
ensure that the user does not try to "Rush" the system by clicking the
"Cancel" or "X" buttons repeatedly, or change more data while the update is
in progress.


********** Begin Code *************

private void btnOK_Click(object sender, EventArgs e) {
this.Enabled = false; // Disable the form to prevent more data changes.
Cursor crOld = Cursor.Current;
try {
Cursor.Current = Cursors.WaitCursor;
UpdateDelegate dlgt = new UpdateDelegate(UpdateData);
IAsyncResult ar = dlgt.BeginInvoke(new AsyncCallback(UpdateCallback),
dlgt);
ar.AsyncWaitHandle.WaitOne();
} finally {
Cursor.Current = crOld;
}
}

private delegate void UpdateDelegate();

private int UpdateData() {
// Update the data, simulating a SLOW network connection.
System.Threading.Thread.Sleep(10000);
return (0);
}

private void UpdateCallback(IAsyncResult ar) {
UpdateDelegate dlgt = (UpdateDelegate) ar.AsyncState;
int nResult = dlgt.EndInvoke(ar);

// Switch back to the UI thread
this.Invoke(new UpdateCompleteDelegate(UpdateComplete), new object[]
{nResult});
}

private void UpdateCompleteDelegate(int result);

private void UpdateComplete(int result) {
if (result == 0) {
this.Close();
} else {
this.Enabled = true;
MessageBox.Show("Update failed. Error " + result.ToString());
}
}

*********** End Code *************

Thanks
 
J

Jon Skeet [C# MVP]

Michael Tumy said:
I have an application that could do some lengthy updates of a
database upon closing a form. So to keep the UI responsive, I'm
pushing the updates off to a worker thread. I seen numerous articles
on using BeginInvoke on delgates to keep you UI responsive. In these
articles, they do a "WaitOne()" call on the UI thread while the
worker thread is processing the data.

Which articles say that? I don't remember ever seeing that mentioned in
an article about UI threading. It's certainly not required, and as you
say, it's stopping the UI thread from working.
This confuses me. How can the UI thread be responsive if it's blocked via
the WaitOne call? I've found that my UI does not redraw if I switch from
this app to another, then back while the update is in progress.

Indeed. The important thing isn't using BeginInvoke on a delegate, IME
- it's using BeginInvoke from another thread in order to make sure that
all the UI updates are done on the UI thread. Using BeginInvoke on a
delegate just gets the thread-pool to handle the work instead of
creating a new thread.
 
M

Michael Tumy

Jon, thanks for the reply.

The article I was using as a reference was from a MS Support Engineer found
in the newsgroups at
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=4YD168npCHA.2072@cpmsftngxa06.

What pointed me to this article was that in the beginning of my button
click, I would change the current cursor to WaitCursor, and then in my
AsyncCallback, change it back to Default. When I did this my cursor would
change back to default when the button click exited after spawing the worker
thread instead of waiting until the callback was called.

Do you know of another way to have to cursor stick upon exit of the click
event? If I were to create my own thread, I think I would still have the
same problem because of the following note from the Cursor.Current remark in
the MSDN: "If you call Application.DoEvents before resetting the Current
property back to the Cursors.Default cursor, the application will resume
listening for mouse events and will resume displaying the appropriate Cursor
for each control in the application."

Here is my original code before I found the above newsgroup article.


********** Begin Code *************

private void btnOK_Click(object sender, EventArgs e) {
this.Enabled = false; // Disable the form to prevent more data changes.
Cursor.Current = Cursors.WaitCursor;
UpdateDelegate dlgt = new UpdateDelegate(UpdateData);
dlgt.BeginInvoke(new AsyncCallback(UpdateCallback), dlgt);
}

private delegate void UpdateDelegate();

private int UpdateData() {
// Update the data, simulating a SLOW network connection.
System.Threading.Thread.Sleep(10000);
return (0);
}

private void UpdateCallback(IAsyncResult ar) {
UpdateDelegate dlgt = (UpdateDelegate) ar.AsyncState;
int nResult = dlgt.EndInvoke(ar);

// Switch back to the UI thread
this.Invoke(new UpdateCompleteDelegate(UpdateComplete), new object[]
{nResult});
}

private void UpdateCompleteDelegate(int result);

private void UpdateComplete(int result) {
Cursor.Current = Cursors.Default;
if (result == 0) {
this.Close();
} else {
this.Enabled = true;
MessageBox.Show("Update failed. Error " + result.ToString());
}
}

*********** End Code *************

Thanks
 
J

Jon Skeet [C# MVP]

Michael Tumy said:
The article I was using as a reference was from a MS Support Engineer found
in the newsgroups at
http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=4YD1
68npCHA.2072%40cpmsftngxa06.

What pointed me to this article was that in the beginning of my button
click, I would change the current cursor to WaitCursor, and then in my
AsyncCallback, change it back to Default. When I did this my cursor would
change back to default when the button click exited after spawing the worker
thread instead of waiting until the callback was called.

Do you know of another way to have to cursor stick upon exit of the click
event? If I were to create my own thread, I think I would still have the
same problem because of the following note from the Cursor.Current remark in
the MSDN: "If you call Application.DoEvents before resetting the Current
property back to the Cursors.Default cursor, the application will resume
listening for mouse events and will resume displaying the appropriate Cursor
for each control in the application."

I'm afraid I don't know much about cursors - but certainly the code
given looks dodgy to me.
 
J

Justin Rogers

Windows Forms does a lot of cursor setting in the background. The only
true way to implement your feature is going to be to override the public Cursor
property on your form, and use state in order to return the current cursor that
you'd like for your application to show.

I have a rather hacky/contrived example that I could give you, but I think it
would
do more harm than good. If you still want the contrived example let me know.
If not, I'll be posting on my blog the esoterics of cursor control very soon,
since
most users are always curious why they set the cursor and it gets reset by some
other property later on.

--
Justin Rogers
DigiTec Web Consultants, LLC.
Blog: http://weblogs.asp.net/justin_rogers
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top