Worker thread passing back messages

C

Craig Lister

I am brand new to threads. I have created a small home at that uploads
a file to an FTP server. It froze up my GUI. So, I changed it to use a
thread to do the uploading:

private void button1_Click(object sender, EventArgs e)
{

Thread thread = new Thread(new ParameterizedThreadStart
(Uploader.UploadFile));
thread.Start(files);

while (thread.IsAlive)
{
Thread.Sleep(1);
this.Invalidate();
this.Refresh();
}

this.Text = "Thread Done!";

}

'files' is a List<> of files that will be uploaded.

This works well, but... I'd like to show a progress bar. Is there a
way for the thread to send a string back to the parent thread, which I
can the use to populate a Label?
 
A

Alberto Poblacion

Craig Lister said:
I am brand new to threads. I have created a small home at that uploads
a file to an FTP server. It froze up my GUI. So, I changed it to use a
thread to do the uploading:

private void button1_Click(object sender, EventArgs e)
{

Thread thread = new Thread(new ParameterizedThreadStart
(Uploader.UploadFile));
thread.Start(files);

while (thread.IsAlive)
{
Thread.Sleep(1);
this.Invalidate();
this.Refresh();
}

this.Text = "Thread Done!";

}

'files' is a List<> of files that will be uploaded.

This works well, but... I'd like to show a progress bar. Is there a
way for the thread to send a string back to the parent thread, which I
can the use to populate a Label?

All the threads share variables in memory. So if you declare in your
class a variable such as "int percentCompleted;", then your thread will be
able to write values into that variable that can be read from the main
thread. However, depending on what you are sharing in your variables, you
need to be careful so that you don't read some data in one thread just as it
is in the middle of being written from the other thread. .Net contains
various different synchronization mechanisms; the one that is probably
simplest to use (but not necessarily optimal) is the lock(...){...}
statement.

However, if your program is a WinForms aplication, writing yourself all
of the preceding is probably overkill. There is already a BackgroundWorker
component that can do all of this for you. It can start the background
thread and notify you when it is done. It also has a ReportProgress method
that you can call in your background thread, which will fire a
ProgressChanged event in the main thread.
 
C

Craig Lister

Thank you,

I will investigate this BackgroundWorker route, and probably end up
asking for more direction.

Thanks.
 
P

Peter Duniho

Craig said:
I am brand new to threads. I have created a small home at that uploads
a file to an FTP server. It froze up my GUI. So, I changed it to use a
thread to do the uploading:

private void button1_Click(object sender, EventArgs e)
{

Thread thread = new Thread(new ParameterizedThreadStart
(Uploader.UploadFile));
thread.Start(files);

while (thread.IsAlive)
{
Thread.Sleep(1);
this.Invalidate();
this.Refresh();
}

this.Text = "Thread Done!";

}

The above code is little better than just executing your network i/o in
smaller chunks and calling Refresh() between chunks (which is to say,
not good at all). Also, calling Invalidate() before Refresh() is
redundant (Refresh() implies an invalidation of the entire control), and
the above doesn't actually "unfreeze" your GUI. It only forces it to
redraw every time the thread becomes runnable (which not only is a waste
of CPU time, but doesn't provide for any user input or interaction).

What you really need to do is start a thread in your Click event
handler, and then return immediately. Let the main GUI thread operate
normally (disable controls and/or operations as appropriate while the
worker thread is doing its processing). Then have some mechanism, if
needed, for the worker thread to report status back.

For example, call a method or otherwise execute some code that
encapsulates the logic that should execute when the thread is done (in
this case, using the Control.Invoke() method to ensure that the access
of the Text property is done on the correct thread):

class Form1
{
private void button1_Click(object sender, EventArgs e)
{
Button1Enabled = false;

new Thread(delegate()
{
Uploader.UploadFile(files, this);
}).Start();
}

// Public property so Uploader class can re-enable
// button. In reality, this is a pretty poor way to
// implement this behavior, due to the tying of the
// Uploader class to the Form1 class. But it's outside
// the scope of this example to demonstrate more-correct
// techniques for that particular issue.
public bool Button1Enabled
{
get { return button1.Enabled; }
set { button1.Enabled = value; }
}
}

class Uploader
{
// For the purpose of the example, I've simply passed the
// reference to the Form1 instance to this method.
// See comment for Form1.Button1Enabled property for
// why such a poor technique is shown here. :)
public static void UploadFile(List<string> files, Form1 form)
{
// do stuff

form.Invoke((MethodInvoker)delegate
{
form.Text = "Thread Done!";
form.Button1Enabled = true;
}
}
}


Or alternatively, if you use BackgroundWorker as Alberto suggests, just
add an event handler to the RunWorkerCompleted event for your
BackgroundWorker instance:

class Form1
{
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;

BackgroundWorker bw = new BackgroundWorker();

bw.DoWork += (sender, e) =>
{
Uploader.UploadFile(files);
};

bw.RunWorkerCompleted += (sender, e) =>
{
this.Text = "Thread Done!";
button1.Enabled = true;
};

bw.RunWorkerAsync();
}
}

Note the use of lambda expressions to create anonymous methods, for
additional conciseness. :)
'files' is a List<> of files that will be uploaded.

This works well, but... I'd like to show a progress bar. Is there a
way for the thread to send a string back to the parent thread, which I
can the use to populate a Label?

The normal idiom in the System.Windows.Forms namespace is to use the
BackgroundWorker. Using that class, all interaction between the worker
thread and your main GUI thread is mediated via the BackgroundWorker.
I.e. with the ProgressChanged and RunWorkerCompleted events, both of
which are automatically raised on the main GUI thread.

Be careful to note, however, that while calling ReportProgress() raises
the ProgressChanged event on the main GUI thread (assuming the
BackgroundWorker instance is created on that thread), it uses a "post"
rather than a "send" to marshal the invocation to raise the event. What
that means is that your event handler is executed asynchronously
relative to the worker thread.

So while doing so automatically deals with the thread affinity issue
inherent in the System.Windows.Forms namespace (i.e. the issue that,
with the exception of a handful of members documented otherwise, all
access of members of Control and its subclasses must occur on the main
GUI thread), it does _not_ deal with synchronization for access to the
data shared by threads, because the ProgressChanged event handler can be
executing at the same time as your worker thread code (the DoWork event
handler, if you're using BackgroundWorker).

To address data synchronization, you can:

-- only pass immutable data or value types, such as a string or an
int, or...

-- deal with the cross-thread invocation more directly, by using
the Control.Invoke() method (so that the worker thread blocks until the
invoked delegate is done doing its work on the main GUI thread), or...

-- synchronize the data sharing somehow

My preference is generally the first option. It requires no specific
synchronization other than that required for the call to
ReportProgress(), and immutable or copied data is a tried-and-true
approach to safe inter-thread communication.

The second option has the benefit of being an easily-recognized pattern
in Forms code, but of course it can really slow processing down (even
more than normal synchronization), especially on a multi-CPU system,
because it winds up serializing not just access to shared data
structures, but across the entire operation being invoked.

The third option is stated simply, but of course there's a wide range of
way to actually implement the synchronization, so it's a misleadingly
simple statement. :) As Alberto says, the "lock" statement is the
simplest, most straightforward approach, but even that requires some
care to use properly, and other techniques just get more complicated
form there.

Hope that helps.

Pete
 
P

Peter Duniho

In case anyone's still interested, I finally remembered that I wanted to
follow up my previous reply, in which I posted some code that I
advertised up-front as not very good, but which I really should have
just provided a better version of. Gotta stop replying to posts in the
wee hours...

Anyway, this code example:
[...]
For example, call a method or otherwise execute some code that
encapsulates the logic that should execute when the thread is done (in
this case, using the Control.Invoke() method to ensure that the access
of the Text property is done on the correct thread):

class Form1
{
private void button1_Click(object sender, EventArgs e)
{
Button1Enabled = false;

new Thread(delegate()
{
Uploader.UploadFile(files, this);
}).Start();
}

// Public property so Uploader class can re-enable
// button. In reality, this is a pretty poor way to
// implement this behavior, due to the tying of the
// Uploader class to the Form1 class. But it's outside
// the scope of this example to demonstrate more-correct
// techniques for that particular issue.
public bool Button1Enabled
{
get { return button1.Enabled; }
set { button1.Enabled = value; }
}
}

class Uploader
{
// For the purpose of the example, I've simply passed the
// reference to the Form1 instance to this method.
// See comment for Form1.Button1Enabled property for
// why such a poor technique is shown here. :)
public static void UploadFile(List<string> files, Form1 form)
{
// do stuff

form.Invoke((MethodInvoker)delegate
{
form.Text = "Thread Done!";
form.Button1Enabled = true;
}
}
}

....includes some fairly awkward ingredients, designed to allow the
proper cross-thread invocation, but in a way that forces inappropriate
tying of the classes (including an unnecessary modification of the
Uploader.UploadFile() method's argument list) as well as a silly
special-purpose property.

A better example along the same lines would look more like this
(ironically, a lot more like my BackgroundWorker example, just without
the BackgroundWorker):

class Form1
{
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;

new Thread(delegate()
{
// This part corresponds to the BackgroundWorker.DoWork
// event handler:
Uploader.UploadFile(files);

// This part corresponds to the
// BackgroundWorker.RunWorkerCompleted event handler:
Invoke((MethodInvoker)delegate
{
Text = "Thread Done!";
button1.Enabled = true;
}
}).Start();
}
}

If there's any chance at all that the UploadFile() method might throw an
exception, the "RunWorkerCompleted" section of the code ought to be in
the "finally" clause of a try/finally block.

As you can see, when done in a more sensible way than as shown in my
original post, it's really not all that different from the
BackgroundWorker implementation. Just slightly different ways of
accomplishing the same thing.

Pete
 
C

Craig Lister

So close. :)

I am trying this:

GoButton.Enabled = false;

foreach (UploadFile file in files)
{
new Thread(delegate()
{
Uploader up = new Uploader(file);
string url = up.UploadFile();

// This part corresponds to the
// BackgroundWorker.RunWorkerCompleted event
handler:
Invoke((MethodInvoker)delegate
{
ItemsListView.Items.Add(new ListViewItem
(url));

});
}).Start();
GoButton.Enabled = true;

Problem is, the same file ends up on the server, twice (If I select 2
files). So, I think it's a threading issue. I think I need to startthe
first upload.. wait for it to complete, then start the second upload..
and so on. How can this be achieved? The UploadFile method returns a
path to the newly uploaded file.
 
P

Peter Duniho

Craig said:
So close. :)

I am trying this:

GoButton.Enabled = false;

foreach (UploadFile file in files)
{
new Thread(delegate()
{
Uploader up = new Uploader(file);
string url = up.UploadFile();

// This part corresponds to the
// BackgroundWorker.RunWorkerCompleted event
handler:
Invoke((MethodInvoker)delegate
{
ItemsListView.Items.Add(new ListViewItem
(url));

});
}).Start();
GoButton.Enabled = true;

Problem is, the same file ends up on the server, twice (If I select 2
files). So, I think it's a threading issue.

No, it's a variable capturing issue that only winds up being a problem
because of the threading. It's good to modify sample code to suit your
needs, but if you do, you have to make sure you understand everything
about the sample code first. :)

In this particular case, your problem is that the "file" variable is
actually instantiated only once (just like a regular "for" loop). Each
new anonymous method created for a thread entry point shares the same
variable. So, each method will use the value of the variable visible to
it whenever it actually _executes_, not when the anonymous method and
Thread instance was created.

In the worst case, all of the threads get created before the first one
even gets to run, and so all of the threads wind up using the same value
for the "file" variable. This can happen if you have only a small
number of threads. If you have a large number of threads, usually
you'll find bunches of threads all using the same value, but more than
one value gets used (and many values being ignored, because no thread
was executing when the variable had that value).
I think I need to startthe
first upload.. wait for it to complete, then start the second upload..
and so on. How can this be achieved?

Yes, I agree that would be the simplest solution. And to do that, you
just rearrange the code so that the "foreach" loop is inside the
anonymous method. To some extent, that's likely a desirable thing to do
anyway, even ignoring the capturing issue.

It would be possible to fix the issue simply by declaring a new variable
_inside_ the "foreach" loop, and assign the "file" variable to it, and
then use that new variable in your anonymous method. Because the new
variable is inside the block of the loop, you get a new captured
instance each time through the loop. That is, each anonymous method
gets its own private copy of the variable, rather than them all sharing.

But, looking at the nature of the work you're trying to do, it probably
doesn't make sense to start as many upload operations simultaneously as
you have files. If this is client-side code, it is almost certainly not
something you want to do. It would just winds up making all the uploads
take longer, as they all fight with each other for the network resource.

If the code is executing on a server, likely to have a LOT more
bandwidth than any one connected client, then it might make more sense
to allow for simultaneous transfers. But even in that case, you'd want
to include logic to limit the number of simultaneous transfers to
something the server can reasonably expect to maximize the usage of the
network. Otherwise, again...you'll wind up with a lot of contention for
the network resource, causing less efficient file transfers.

Also, if you rearrange the loop so the "foreach" is inside the anonymous
method, then that fixes the problem you have with your re-enabling of
your "GoButton" before all the transfers are actually finished. :)

So, with those modifications, the code would look something like this:

class Form1
{
private void button1_Click(object sender, EventArgs e)
{
GoButton.Enabled = false;

new Thread(delegate()
{
foreach (UploadFile file in files)
{
Uploader up = new Uploader(file);
string url = up.UploadFile();

Invoke((MethodInvoker)delegate
{
ItemsListView.Items.Add(url);
});
}

Invoke((MethodInvoker)delegate
{
GoButton.Enabled = true;
}
}).Start();
}
}

Pete
 

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