Threading: ThreadStart vs BeginXXX/EndXXX

  • Thread starter Thread starter AMI
  • Start date Start date
A

AMI

I started with a fairly simple task: Displaying a progressbar on a WinForm
for a lengthy download over HTTP. I soon arrived at the question: What is
the best way to start a worker thread and why?

As I see it, the framework gives us the ThreadStart delegate to do this, and
also the BeginXXX/EndXXX model for asynchronous operations. The ThreadStart
delegate usage is quite simple to understand. I've come to understand the
other model, and now my refined question is: What benefits of going with
the other model?

I wrote some pseudocode below to illustrate the two different approaches to
the same problem, and I can't see why the heck one would choose the latter.
Thanks for any feedback!

------------------
ThreadStart approach
------------------
private void MainMethod()
{
Thread thread = new Thread(new ThreadStart(this.DownloadFile));
thread.Start();
thread.Join();
MessageBox.Show("I'm done.")
return;
}

private void DownloadFile()
{
// Do some work
// Update progressbar
// Do more work
// Update progressbar
}



--------------------------
Asynchronous method approach
--------------------------
private void MainMethod()
{
IAsyncResult result = BeginDownloadFile(new
AsyncCallback(EndDownloadFile));
result.WaitHandle.WaitOne();
MessageBox.Show("I'm done.")
return;
}

private IAsyncResult BeginDownloadFile(AsyncCallBack callback)
{
// Set up my own worker thread, point it to DownloadFile using
ThreadStart
// Somehow register the callback method to a Thread complete event (????)
// Start my thread
// Package up my homerolled MyAsyncResult object and return it
}

private void DownloadFile()
{
// Do some work
// Update progressbar
// Do more work
// Update progressbar
}

private void EndDownloadFile(IAsyncResult result)
{
...
}

public class MyAsyncResult : IAsyncResult
{
...
}
 
AMI said:
I started with a fairly simple task: Displaying a progressbar on a WinForm
for a lengthy download over HTTP. I soon arrived at the question: What is
the best way to start a worker thread and why?

As I see it, the framework gives us the ThreadStart delegate to do this, and
also the BeginXXX/EndXXX model for asynchronous operations. The ThreadStart
delegate usage is quite simple to understand. I've come to understand the
other model, and now my refined question is: What benefits of going with
the other model?

I wrote some pseudocode below to illustrate the two different approaches to
the same problem, and I can't see why the heck one would choose the latter.
Thanks for any feedback!

BeginXXX/EndXXX typically *don't* start an extra thread - they use the
ThreadPool instead. Of course you *could* start an extra thread, but if
so you should warn people about this behaviour. You could use a custom
thread pool as well, instead of the system thread pool.

As for which is better, it really depends on how your class is going to
be used. Note that start a new thread and then joining it immediately
is almost always entirely pointless.
 
AMI said:
I started with a fairly simple task: Displaying a progressbar on a WinForm
for a lengthy download over HTTP. I soon arrived at the question: What
is
the best way to start a worker thread and why?

....

There's a better question, and perhaps you already know the answer:

....
private void DownloadFile()
{
// Do some work
// Update progressbar
// Do more work
// Update progressbar
}

Perhaps you know that you can't directly update the progress bar from
another thread? In fact, you can't directly touch any part of the UI from
another thread. Instead, you need to use Control.Invoke to marshall all of
your UI work to the UI thread:

private void UpdateProgressBar(ProgressBar bar, int progressValue)
{
bar.Value = progressValue;
}

private delegate void ProgressBarHandler(ProgressBar bar, int
progressValue);

private void DownLoadFile()
{
// Do some work
// Update progressbar
progressBar.Invoke(new ProgressBarHandler(UpdateProgressBar), new
object[]{progressValue});
// Do more work
// Update progressbar
progressBar.Invoke(new ProgressBarHandler(UpdateProgressBar), new
object[]{progressValue});
}

of course, you can get fancy and hide all the Invoking inside of
UpdateProgressBar:

private void UpdateProgressBar(ProgressBar bar, int progressValue)
{
if (bar.InvokeRequired)
{
bar.Invoke(new ProgressBarHandler(UpdateProgressBar), new
object[]{progressValue});
}
else
{
bar.Value = progressValue;
}
}


You probably knew this, but other readers might not have known...

John Saunders

P.S. I learned the hard way that this rule also applies to modifying objects
which might raise events to the UI. I once had an application in which the
main form displayed a DataGrid. The DataGrid was bound to a DataSet. The
DataSet was being updated in a background thread.

Bad Idea, as the DataGrid had created a DataView against the DataSet, and
was listening to the ListChanged event of that DataView. My innocent
addition of a row raised ListChanged - directly from my background thread to
the DataGrid. Not a pretty sight.
 
BeginXXX/EndXXX typically *don't* start an extra thread - they use the
ThreadPool instead. Of course you *could* start an extra thread, but if
so you should warn people about this behaviour. You could use a custom
thread pool as well, instead of the system thread pool.

Interesting, I didn't know that. Be that as it may, if I was able to mimic
the ThreadPool behavior of BeginXXX/EndXXX, what then is the difference when
compared to ThreadStart? I'm still trying to understand why there are two
different models for asynchronous operations, and what the significance of
the difference is.

Given the relative complexity of BeginXXX/EndXXX, is it more a matter of
form practiced by authors of class libraries rather than application
developers? This too seems odd, since every method has the ability to be
executed asynchronously via the ThreadStart approach.

Note that start a new thread and then joining it immediately
is almost always entirely pointless.

One benefit of it would be that the main thread would be available to update
the progress bar, right? Or does Thread.Join() block so that _nothing_
happens on that thread?
 
AMI said:
Interesting, I didn't know that. Be that as it may, if I was able to mimic
the ThreadPool behavior of BeginXXX/EndXXX, what then is the difference when
compared to ThreadStart? I'm still trying to understand why there are two
different models for asynchronous operations, and what the significance of
the difference is.

BeginXXX/EndXXX is usually (IME) used for IO operations which don't
actually require a thread to be doing anything until something external
happens.

Sometimes it's more appropriate to use that model - other times you
want to start a whole other thread.
Given the relative complexity of BeginXXX/EndXXX, is it more a matter of
form practiced by authors of class libraries rather than application
developers? This too seems odd, since every method has the ability to be
executed asynchronously via the ThreadStart approach.

Yes, it's fairly rare to implement BeginXXX/EndXXX yourself. Don't
forget that you can always use BeginInvoke/EndInvoke on arbitrary
delegates, if you want.
One benefit of it would be that the main thread would be available to update
the progress bar, right? Or does Thread.Join() block so that _nothing_
happens on that thread?

The latter - almost.

See http://blogs.msdn.com/cbrumme/archive/2003/04/17/51361.aspx

It's all a bit difficult, but it's pretty nasty to rely on any pumping
being done during Thread.Join. Treat it as a thoroughly blocking
method.
 
Makes sense, but then I seem to be back at a simple WinForms question, which
is:

What is the correct threading technique for invoking DoLengthyOperation() in
response to a button click (and not wanting my form to freeze up, of
course)?
 
AMI said:
Makes sense, but then I seem to be back at a simple WinForms question, which
is:

What is the correct threading technique for invoking DoLengthyOperation() in
response to a button click (and not wanting my form to freeze up, of
course)?

Start a new thread, and get that to call back into the form (using
Invoke or BeginInvoke) when it's finished.
 
Back
Top