Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Languages / C# / March 2008

Tip: Looking for answers? Try searching our database.

Threading issue: Timers.Timer and Forms.BackgroundWorker

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Arthur - 28 Mar 2008 19:27 GMT
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
Ignacio Machin ( .NET/ C# MVP ) - 28 Mar 2008 20:01 GMT
> I developed a simple windows forms application (for controlling a
> service application).
[quoted text clipped - 93 lines]
> Thanks in advance,
> Arthur

Hi,

You should use Control.Invoke , you can search the archives of this NG
as this is a recurrent question.
adamroot@microsoft.com - 28 Mar 2008 20:25 GMT
> I developed a simple windows forms application (for controlling a
> service application).
[quoted text clipped - 93 lines]
> 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!";
}
adamroot@microsoft.com - 28 Mar 2008 20:28 GMT
On Mar 28, 12:25 pm, adamr...@microsoft.com wrote:

> > I developed a simple windows forms application (for controlling a
> > service application).
[quoted text clipped - 124 lines]
>
> - 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
Peter Duniho - 28 Mar 2008 21:02 GMT
> [...]
> 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
vvnraman - 28 Mar 2008 21:11 GMT
On Mar 29, 1:02 am, "Peter Duniho" <NpOeStPe...@nnowslpianmk.com>
wrote:
> > [...]
> > So what about the backgroundworker? Can anyone explain the pros of the
[quoted text clipped - 29 lines]
>
> 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

Rate this thread:







Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.