Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Mulitihreading problem with BackgroundWorker

0 views
Skip to first unread message

Jan

unread,
Dec 18, 2009, 1:12:29 PM12/18/09
to
I am new to multithreading, and I am having trouble getting the main form to
update with status information.

I have a main form with a label to receive updates of the status of a
long-running process (in its text property).

The thread is a background worker, and I run a method in a class separate
from the main form. The code that starts the thread is:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;

for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;

break;
}
else
{
Positions.Start(this);
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}

On my main form, I have created the following delegate and method:

public delegate void UpdateTextCallback(string text);

public void UpdateText(string text)
{
// Set the textbox text.
this.lbl_Processing.Text = text;
}

Within the class that executes the long-running process, I have tried
several ways of invoking the delegate that updates the text, but none will
compile correctly.

Given this scenario, how can I get the label text property to update from a
class running on a different thread?

Thanks very much for any help.

Jan

Peter Duniho

unread,
Dec 18, 2009, 2:12:11 PM12/18/09
to
Jan wrote:
> [...]

> Within the class that executes the long-running process, I have tried
> several ways of invoking the delegate that updates the text, but none will
> compile correctly.
>
> Given this scenario, how can I get the label text property to update from a
> class running on a different thread?

Under what conditions do you want the text to be updated? If it's when
the progress is reported by your background worker task, then IMHO the
easiest thing to do is pass the string to the ReportProgress() method as
the second argument. Then your ProgressChanged event handler can get
the reference via the ProgressChangedEventArgs.UserState property, cast
it back to a string, and assign to the appropriate Text property.

If that's not helpful, you need to provide a more complete code example.
There's nothing in the example you've shown that explains what
"Positions.Start(this)" does or why it's relevant to the example at all,
nor shows how you attempted to invoke the callback you tried to declare
or in what situation you want to invoke the delegate.

Pete

Jan

unread,
Dec 18, 2009, 3:20:43 PM12/18/09
to
Positions is a class and the Start method is the entry point to a financial
analysis program that uses 12 different programs in three classes, so in the
interest of brevity, I didn't post more. The program can take 10-20 minutes
to complete and runs through many iterations of different scenarios.

All of the examples I have seen in my research show everything happening in
the Form class. I pass a reference to the main form to the Positions class
(Positions.Start(this)) so that the Positions class can reference data on
the form (we can't use the "this" keyword in a class separate from the Form
class). Now that Positions runs in a separate thread I can't see how to
address a label or a text box residing on the main form.

In the examples I have seen, all of the code is contained within the DoWork
method, and ReportProgress is called from within DoWork. However, in my
code, DoWork must call Positions, which is in another class. In brief, it
looks like this:

public partial class Positions
{
public static void Start (Form1 frm)
{

. . .

// Here I would like to update frm.label1.text but I get a compiler error
that it was created on a different thread.
// I have tried the Invoke method on the label, but now the compiler doesn't
recognize it for the same reason.

}

So the problem comes when we go into a class separate from the Form class.
If, given that, you can shed any more light on this I would be grateful.

Thanks again.

"Peter Duniho" <no.pet...@no.nwlink.spam.com> wrote in message
news:uC1RBYBg...@TK2MSFTNGP04.phx.gbl...

Peter Duniho

unread,
Dec 18, 2009, 3:56:10 PM12/18/09
to
Jan wrote:
> Positions is a class and the Start method is the entry point to a financial
> analysis program that uses 12 different programs in three classes, so in the
> interest of brevity, I didn't post more. The program can take 10-20 minutes
> to complete and runs through many iterations of different scenarios.

Then I guess the converse question is, if you are including the actual
method you call to do actual work, why is there a call to Thread.Sleep()
in your example code? That's something normally you'd see only if you
want to _simulate_ some time-consuming task. If you have a method call
that actually executes a time-consuming task, there's no need to
simulate that.

> All of the examples I have seen in my research show everything happening in
> the Form class. I pass a reference to the main form to the Positions class
> (Positions.Start(this)) so that the Positions class can reference data on
> the form (we can't use the "this" keyword in a class separate from the Form
> class).

Well, you can always use "this", but it means something different for
each instance of each class, of course.

As far as passing "this" to the Positions class, don't do that. Your
calculations code, encapsulated by the Positions class, should not know
anything at all about your user interface.

> Now that Positions runs in a separate thread I can't see how to
> address a label or a text box residing on the main form.

You seem to be confusing "thread" with "class". The members of the form
class are private, so they are not visible to any code not in the form
class itself.

They _are_ visible to code executing in a different thread, as long as
that code is in fact in the form class. Of course, you still have the
issue that objects in the System.Windows.Forms namespace have "affinity"
for the thread on which they are created; you are required to access the
members of those objects only in the thread that owns the object. But
that's not a visibility issue.

> In the examples I have seen, all of the code is contained within the DoWork
> method, and ReportProgress is called from within DoWork. However, in my
> code, DoWork must call Positions, which is in another class.

You can pass the BackgroundWorker instance to your Start() method,
rather than the Form instance reference. The ReportProgress() method is
a public method, and so is visible to code outside the BackgroundWorker
class (unlike the form members).

Of course, this assumes your Start() method is the one that needs to
report progress. In your code example, you seem satisfied to report
progress after each call to the Start() method, and that's fine too.
Then you need not pass anything to the Start() method; just make sure it
returns whatever information/string you need and let the caller of
Start() call the ReportProgress() method also.

In either case, then your form code can subscribe to the ProgressChanged
event in the BackgroundWorker instance, and it will receive notification
any time the calculation class calls the ReportProgress() method. Even
better, the BackgroundWorker class itself will automatically deal with
the thread affinity issue, raising the ProgressChanged event on the
thread that owns your form.

This means that your event handler can just do whatever you need it to
do (e.g. assigning a string value to some Text property of some control
instance), without any additional effort on your part to deal with the
thread affinity issue (e.g. by calling Control.Invoke() to execute the
actual code you need to execute).

> In brief, it
> looks like this:
>
> public partial class Positions
> {
> public static void Start (Form1 frm)
> {
>

> .. . .


>
> // Here I would like to update frm.label1.text but I get a compiler error
> that it was created on a different thread.

No, you don't get such a compiler error. The compiler has no idea what
thread the code is executing on. You either get a compiler error that
you are trying to access a class member that's not visible, or you get a
run-time error that the object was created on a different thread.

> // I have tried the Invoke method on the label, but now the compiler doesn't
> recognize it for the same reason.

Your "label1" field in the form class is private, and so isn't
accessible to code not in the form class itself.

> So the problem comes when we go into a class separate from the Form class.
> If, given that, you can shed any more light on this I would be grateful.

Let's assume the "bw_DoWork" method you posted earlier is in your form
class. Then, you should be able to change things so that it looks like
this:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{

BackgroundWorker worker = (BackgroundWorker)sender;

// This next statement, declaring and subscribing the event
// handler, could actually be put outside of the DoWork
// event handler, where the BackgroundWorker is actually
// started by calling RunWorkerAsync().
// If you are using the Designer to configure the
// BackgroundWorker, you could even just double-click
// the "ProgressChanged" event in the Designer
// and write the event handler there. Either is probably
// actually preferable to this, but this works and it's
// a lot easier for the purpose of the code example than
// trying to guess what the rest of your code looks like.

worker.ProgressChanged += (sender, e) =>
{
lbl_Processing.Text = (string)e.UserState;
};

for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;

break;
}

worker.ReportProgress((i * 10), Positions.Start());
}
}

Then in your Positions class:

class Positions
{
public string Start()
{
string strText;

// some work

// something that initializes strText

return strText;
}
}

Note that there are a large number of similar discussions in this
newsgroup, which you can find using Google Groups to search the archive.
There are even other messages on the topic less than 24 hours old,
besides the ones in this message thread.

Pete

Jan

unread,
Dec 18, 2009, 5:20:25 PM12/18/09
to
Pete,

Thanks for your detailed reply. You have given me a lot of useful
information. I found some info on the Net that advised to create a new
event in the Form class and subscribe to it from other classes, but you
suggest creating the event in the class that actually raises the event and
subscribe to it from the Form class, which makes more sense and sounds less
cumbersome.

To answer a few of your queries, (1) the call to Thread.Sleep is beginner's
clumsiness; it shouldn't be there; (2) controls on the main form can be
made public, just edit the Designer code and change "private" to "public." I
do it all the time in my non-threaded applications. To access info in a
text box on the form, I can do this:

Public static void Start (Form1 frm)
{
string info = frm.textbox1.Text;
}

It works if I've changed textbox1 from private to public, but I guess it's
not good practice.

As ReportProgress() is a public method, it sounds like it would be best to
use it from within the classes.

Thanks again for your very useful info. I'll be able to get this to work
now.

Jan

"Peter Duniho" <no.pet...@no.nwlink.spam.com> wrote in message

news:eghMKSCg...@TK2MSFTNGP04.phx.gbl...

Peter Duniho

unread,
Dec 18, 2009, 5:43:41 PM12/18/09
to
Jan wrote:
> Pete,
>
> Thanks for your detailed reply. You have given me a lot of useful
> information. I found some info on the Net that advised to create a new
> event in the Form class and subscribe to it from other classes, but you
> suggest creating the event in the class that actually raises the event and
> subscribe to it from the Form class, which makes more sense and sounds less
> cumbersome.

Yes. You may have misunderstood the advice you found elsewhere, either
in its specifics, or with regard to the situation it was intended for,
or the advice could simply have been bad. But given you problem
description so far, declaring an event in your form class isn't going to
help.

> To answer a few of your queries, (1) the call to Thread.Sleep is beginner's
> clumsiness; it shouldn't be there;

Okay.

> (2) controls on the main form can be
> made public, just edit the Designer code and change "private" to "public."

I am quite aware of this.

> I do it all the time in my non-threaded applications.

You shouldn't. If you find yourself making those fields public, you've
almost certainly designed your code poorly. The form class should limit
access to its members, providing client code with only the specific and
minimum interaction needed to solve a particular design problem.

> To access info in a
> text box on the form, I can do this:
>
> Public static void Start (Form1 frm)
> {
> string info = frm.textbox1.Text;
> }
>
> It works if I've changed textbox1 from private to public, but I guess it's
> not good practice.

No, it's not at all.

> As ReportProgress() is a public method, it sounds like it would be best to
> use it from within the classes.

Yes, calling ReportProgess() from the code doing the processing, and
handling the ProgressChanged event in a handler declared by the code
doing the UI, is a much better approach.

Pete

0 new messages