Threading issue: Timers.Timer and Forms.BackgroundWorker

A

Arthur

I developed a simple windows forms application (for controlling a
service application).

As there are no events of the ServiceController class (e.g. "State of
the service applioaction has changed") the forms application can
subscribe to, it polls the state of the service through the
ServiceController class every one seconds.

This is done by a Timer object "t" with a "t_elapsed" eventhandler
subscribed to the "elapsed" event.

When the eventhandler tries to update controls of the form an
exception is thrown, as the controls belong to another thread than the
one where the "t_elapsed" eventhandler runs. the eventhandler runs in
the "Timer" thread as far as i understood the matter.

So far everything works as we all expected it.

Here is a codeexample for the above described:
public partial class Form1 : Form
{
ServiceController sc;
public System.Timers.Timer t;

public Form1()
{
InitializeComponent();

this.sc = new ServiceController("SomeService");
this.t = new System.Timers.Timer(1000);
this.t.Elapsed += new ElapsedEventHandler(t_Elapsed);
this.t.Start();
}

protected void t_Elapsed(object sender, ElapsedEventArgs e)
{
//cannot update controls of the form from here.
}
}


The msdn2 says, the prefered way to solve cross thread control access
issues is to use a backgroundworker. And it plots the way like this:

..add a backgroundworker to the form
..register a handler for its "RunWorkerCompleted" event
..call the "RunWorkerAsync" method, when something needs to be done
..do the updating of the controls safely from the handler that
subscribes to the "RunWorkerCompleted" event

....which imediately arouse the question "Why should this work as the
handler will as well run in another thread than the one that owns the
controls?"

And indeed, it doesn't work this way either.

Here is a code example for the 2nd try:

public partial class Form1 : Form
{
ServiceController sc;
public System.Timers.Timer t;

public Form1()
{
InitializeComponent();

this.sc = new ServiceController("MyFirstService");
this.t = new System.Timers.Timer(1000);
this.t.Elapsed += new ElapsedEventHandler(t_Elapsed);
this.t.Start();
}

protected void t_Elapsed(object sender, ElapsedEventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_RunWorkerCompleted(object
sender, RunWorkerCompletedEventArgs e)
{
//cannot access forms controls from here either
}
}

So what about the backgroundworker? Can anyone explain the pros of the
BGWorker to me and why it does not work in my example (or better, how
i should put it to work)?

My actual solution - which works :) - is to use "BeginnInvoke" in
"t_elapsed" to access the controls through the UI thread.

But i would like to gather knowledge and wisdom about the BGWorker
solution too :)

Thanks in advance,
Arthur
 
I

Ignacio Machin ( .NET/ C# MVP )

I developed a simple windows forms application (for controlling a
service application).

As there are no events of the ServiceController class (e.g. "State of
the service applioaction has changed") the forms application can
subscribe to, it polls the state of the service through the
ServiceController class every one seconds.

This is done by a Timer object "t" with a "t_elapsed" eventhandler
subscribed to the "elapsed" event.

When the eventhandler tries to update controls of the form an
exception is thrown, as the controls belong to another thread than the
one where the "t_elapsed" eventhandler runs. the eventhandler runs in
the "Timer" thread as far as i understood the matter.

So far everything works as we all expected it.

Here is a codeexample for the above described:
public partial class Form1 : Form
{
        ServiceController sc;
        public System.Timers.Timer t;

        public Form1()
        {
                InitializeComponent();

                this.sc = new ServiceController("SomeService");
                this.t = new System.Timers.Timer(1000);
                this.t.Elapsed += new ElapsedEventHandler(t_Elapsed);
                this.t.Start();
        }

        protected void t_Elapsed(object sender, ElapsedEventArgs e)
        {
            //cannot update controls of the form from here.
        }

}

The msdn2 says, the prefered way to solve cross thread control access
issues is to use a backgroundworker. And it plots the way like this:

.add a backgroundworker to the form
.register a handler for its "RunWorkerCompleted" event
.call the "RunWorkerAsync" method, when something needs to be done
.do the updating of the controls safely from the handler that
subscribes to the "RunWorkerCompleted" event

...which imediately arouse the question "Why should this work as the
handler will as well run in another thread than the one that owns the
controls?"

And indeed, it doesn't work this way either.

Here is a code example for the 2nd try:

    public partial class Form1 : Form
    {
        ServiceController sc;
        public System.Timers.Timer t;

        public Form1()
        {
                InitializeComponent();

                this.sc = new ServiceController("MyFirstService");
                this.t = new System.Timers.Timer(1000);
                this.t.Elapsed += new ElapsedEventHandler(t_Elapsed);
                this.t.Start();
        }

        protected void t_Elapsed(object sender, ElapsedEventArgs e)
        {
                this.backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_RunWorkerCompleted(object
sender, RunWorkerCompletedEventArgs e)
        {
            //cannot access forms controls from here either
        }
    }

So what about the backgroundworker? Can anyone explain the pros of the
BGWorker to me and why it does not work in my example (or better, how
i should put it to work)?

My actual solution - which works :) - is to use "BeginnInvoke" in
"t_elapsed" to access the controls through the UI thread.

But i would like to gather knowledge and wisdom about the BGWorker
solution too :)

Thanks in advance,
Arthur

Hi,

You should use Control.Invoke , you can search the archives of this NG
as this is a recurrent question.
 
A

adamroot

I developed a simple windows forms application (for controlling a
service application).

As there are no events of the ServiceController class (e.g. "State of
the service applioaction has changed") the forms application can
subscribe to, it polls the state of the service through the
ServiceController class every one seconds.

This is done by a Timer object "t" with a "t_elapsed" eventhandler
subscribed to the "elapsed" event.

When the eventhandler tries to update controls of the form an
exception is thrown, as the controls belong to another thread than the
one where the "t_elapsed" eventhandler runs. the eventhandler runs in
the "Timer" thread as far as i understood the matter.

So far everything works as we all expected it.

Here is a codeexample for the above described:
public partial class Form1 : Form
{
        ServiceController sc;
        public System.Timers.Timer t;

        public Form1()
        {
                InitializeComponent();

                this.sc = new ServiceController("SomeService");
                this.t = new System.Timers.Timer(1000);
                this.t.Elapsed += new ElapsedEventHandler(t_Elapsed);
                this.t.Start();
        }

        protected void t_Elapsed(object sender, ElapsedEventArgs e)
        {
            //cannot update controls of the form from here.
        }

}

The msdn2 says, the prefered way to solve cross thread control access
issues is to use a backgroundworker. And it plots the way like this:

.add a backgroundworker to the form
.register a handler for its "RunWorkerCompleted" event
.call the "RunWorkerAsync" method, when something needs to be done
.do the updating of the controls safely from the handler that
subscribes to the "RunWorkerCompleted" event

...which imediately arouse the question "Why should this work as the
handler will as well run in another thread than the one that owns the
controls?"

And indeed, it doesn't work this way either.

Here is a code example for the 2nd try:

    public partial class Form1 : Form
    {
        ServiceController sc;
        public System.Timers.Timer t;

        public Form1()
        {
                InitializeComponent();

                this.sc = new ServiceController("MyFirstService");
                this.t = new System.Timers.Timer(1000);
                this.t.Elapsed += new ElapsedEventHandler(t_Elapsed);
                this.t.Start();
        }

        protected void t_Elapsed(object sender, ElapsedEventArgs e)
        {
                this.backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_RunWorkerCompleted(object
sender, RunWorkerCompletedEventArgs e)
        {
            //cannot access forms controls from here either
        }
    }

So what about the backgroundworker? Can anyone explain the pros of the
BGWorker to me and why it does not work in my example (or better, how
i should put it to work)?

My actual solution - which works :) - is to use "BeginnInvoke" in
"t_elapsed" to access the controls through the UI thread.

But i would like to gather knowledge and wisdom about the BGWorker
solution too :)

Thanks in advance,
Arthur

Do you need to use the System.Timers.Timer instead of the
System.Windows.Forms.Timer? The SWF timer supports the component
model, so that when your form is disposed, it will properly dispose. I
say this because you may run into other synchronization problems
(depending when/how do you stop the timer events; ie if your timer
elapses after the form is disposed, then it tries to access the
controls, you will get an exception). Anyway, assuming you have that
under control, you can use something like this in your t_Elapsed
function (forget the BackgroundWorker, it is meant for a different
purpose, although technically you *could* use it):

protected void t_Elapsed(object sender, ElapsedEventArgs e)
{
if (InvokeRequired) // If not on the UI thread ...
{
Invoke(new ElapsedEventHandler(t_Elapsed), sender, e); // ...
Invoke the UI thread (can also use the non-blocking BeginInvoke if
needed)
return; // This thread should not try to access controls
}

// If execution got here, this thread is safe to work with
controls
label1.Text = "Hello World!";
}
 
A

adamroot

Do you need to use the System.Timers.Timer instead of the
System.Windows.Forms.Timer? The SWF timer supports the component
model, so that when your form is disposed, it will properly dispose. I
say this because you may run into other synchronization problems
(depending when/how do you stop the timer events; ie if your timer
elapses after the form is disposed, then it tries to access the
controls, you will get an exception). Anyway, assuming you have that
under control, you can use something like this in your t_Elapsed
function (forget the BackgroundWorker, it is meant for a different
purpose, although technically you *could* use it):

protected void t_Elapsed(object sender, ElapsedEventArgs e)
{
    if (InvokeRequired) // If not on the UI thread ...
    {
        Invoke(new ElapsedEventHandler(t_Elapsed), sender, e); // ....
Invoke the UI thread (can also use the non-blocking BeginInvoke if
needed)
        return; // This thread should not try to access controls
    }

    // If execution got here, this thread is safe to work with
controls
    label1.Text = "Hello World!";



}- Hide quoted text -

- Show quoted text -- Hide quoted text -

- Show quoted text -

I'm sorry, I'm rusty with Timers... My previous post is valid if you
are going to use that specific Timer, but the
System.Windows.Forms.Timer class does the Invoke for you! So using
that class would clean up your code a bit. Check out this article for
in-depth explanations of the differences of the Timer classes in .NET:

http://msdn2.microsoft.com/en-us/magazine/cc164015.aspx
 
V

vvnraman

[...]
So what about the backgroundworker? Can anyone explain the pros of the
BGWorker to me and why it does not work in my example (or better, how
i should put it to work)?

Using BackgroundWorker is in fact preferrable, when it's applicable.
However, the situation you're dealing with isn't applicable to the use of
BackgroundWorker. As Ignacio says, the most direct solution is to just
use Invoke() or BeginInvoke(), as you're doing now.

BackgroundWorker is, as the name implies, more for situations where you
have some task you want to execute in the background, without blocking the
main GUI thread. It's not required for addressing that situation either,
but because the ProgressChanged and RunWorkerCompleted events are
automatically invoked back onto the GUI thread (when you create the
BackgroundWorker from the GUI thread), it provides a very convenient
interface for dealing with the cross-thread issues as long as your
processing fits the specific model that BackgroundWorker exposes.

Most long-running tasks do in fact fit, so it's a good solution for most
long-running tasks. But that's not the scenario you seem to be addressing.

Finally, you may want to consider Adam's advice as well. You're running
into this because of the particular Timer class you chose to use. The
issue doesn't come up with the Forms.Timer class, not because the class
uses Invoke() on your behalf, but rather because that timer uses the
Windows WM_TIMER message and so timer events are automatically dealt with
on the GUI thread (since that's where all window messages are handled).

Unless you have a specific need to use the Timers.Timer class, you may be
better off just using the Forms.Timer class.

Pete

Hi Arthur
Its good if you already know about the BeginInvoke() method.
Anyways i'll illustrate it again along with the BackgroundWorker.

I'll use an example here. Suppose you have an applilcation where
the user can chose any image folder from disk and the app will
display
the thumbnails of those images one by one. Now thumbnail generation
is a relatively slow procedure and if the operation is carried out on
the
UI thread, the app will hang and will go to the NOT RESPONDING state.

The correct way to do this would be :
After the user selects a folder from the disk (using a folderDialog
dialog)
1. Get the list of image files which will be a string array of file
paths and
start a background thread to generate the thumbnail of the image.
2. Report the serial no. of file being processed to the main form from
within
the background thread by raising the ProgressChanged event.
3. Handle the ProgressChanged event of the background thread and then
call BeginInvoke to execute the method which actually displays the
progress
but runs on the UI thread.

I made a small application and its working fine. I don't know how to
upload
files here so i'll copy paste the code.
Its a simple thumbnail browser. Create a new windows application and
1. Label with Text = "Folder Path"
2. TextBox with name "txtFolderPath"
3. Button Text = "Browse" Name = "btnBrowse"
4. Button Text = "Generate Thumbnails" Name = "btnGenerate"
5. Button Text = "Clear Thumbnails" Name = "btnClear"
6. A FlowLayoutPanel
7. A StatusBar and add a StatusLabel and a ProgressBar to it.

Here's the code

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace ThumbnailBrowser
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private BackgroundWorker bgWorker = new BackgroundWorker();

private void btnBrowse_Click(object sender, EventArgs e)
{
using (FolderBrowserDialog fbd = new
FolderBrowserDialog())
{
fbd.ShowNewFolderButton = false;
if (fbd.ShowDialog() == DialogResult.OK)
{
txtFolderPath.Text = fbd.SelectedPath;
}
}
}

private void btnGenerate_Click(object sender, EventArgs e)
{
try
{
string[] sImageFiles =
System.IO.Directory.GetFiles(txtFolderPath.Text, "*.jpg");
if (sImageFiles != null && sImageFiles.Length > 0)
{
bgWorker.DoWork += new
DoWorkEventHandler(bgWorker_GenerateThumbnails);
bgWorker.ProgressChanged += new
ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.WorkerReportsProgress = true;
bgWorker.WorkerSupportsCancellation = true;

bgWorker.RunWorkerAsync(sImageFiles);
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message, "Error");
}
}

public delegate void
UpdateStatusAndAppendThumbnailDelegate(ProgressChangedEventArgs
e);

private void bgWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
object[] oArgs = new object[] { e };
this.BeginInvoke(new
UpdateStatusAndAppendThumbnailDelegate(UpdateStatusAndAppendThumbnail),
oArgs);
}

private void
UpdateStatusAndAppendThumbnail(ProgressChangedEventArgs e)
{
toolStripProgressBar1.Value = e.ProgressPercentage;
toolStripStatusLabel1.Text =
e.ProgressPercentage.ToString() + " % complete.";
AppendThumbnail((Image)e.UserState);
}

private void AppendThumbnail(Image thumbnail)
{
PictureBox pb = new PictureBox();
pb.SizeMode = PictureBoxSizeMode.Zoom;
pb.Image = thumbnail;
this.flowLayoutPanel1.Controls.Add(pb);
}

public bool ThumbnailCallback()
{
return false;
}

private void bgWorker_GenerateThumbnails(object sender,
DoWorkEventArgs e)
{
try
{
BackgroundWorker currBgWorker = sender as
BackgroundWorker;
Size szContentSize = new Size(170, 170);
string[] sImageFiles = (string[])e.Argument;
int i = 0;
int iTotal = sImageFiles.Length;
foreach (string sFile in sImageFiles)
{
i++;
Image img = null;
img = Image.FromFile(sFile);

// Determine scaling for new rectangle:
double xScale = (1.0 * img.Width) /
szContentSize.Width;
double yScale = (1.0 * img.Height) /
szContentSize.Height;
Rectangle destRect;
if (xScale > yScale)
{
if (img.Height / xScale <
szContentSize.Height)
{
destRect = new Rectangle(0, 0, (int)
(img.Width / xScale), (int)(img.Height / xScale));
}
else
{
destRect = new Rectangle(0, 0, (int)
(img.Width / yScale), (int)(img.Height / yScale));
}
}
else
{
if (img.Width / yScale < szContentSize.Width)
{
destRect = new Rectangle(0, 0, (int)
(img.Width / yScale), (int)(img.Height / yScale));
}
else
{
destRect = new Rectangle(0, 0, (int)
(img.Width / xScale), (int)(img.Height / xScale));
}
}
Size szThumbnailSize = destRect.Size;
Image.GetThumbnailImageAbort myCallback = new
Image.GetThumbnailImageAbort(ThumbnailCallback);
Image imgThumbnail = img.GetThumbnailImage(
szThumbnailSize.Width,
szThumbnailSize.Height,
myCallback,
IntPtr.Zero);
object[] oArgs = new object[] { imgThumbnail };
int iProgressPercent = (i*100)/iTotal;
currBgWorker.ReportProgress(iProgressPercent,
imgThumbnail);
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message, "Error");
}
}

private void btnClear_Click(object sender, EventArgs e)
{
foreach (Control ctl in this.flowLayoutPanel1.Controls)
{
ctl.Dispose();
}
this.flowLayoutPanel1.Controls.Clear();
}
}
}

This is working fine.
You can add handlers to cancel the generation of thumbnails and
some other code to prevent the user from clicking the Generate
Thumbnail
button when one operation is already in progress.

Regards
Prateek Raman
 

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