C# Source Code: Marshalling data across threads to update UI components
[
Home
|
Contents
|
Search
|
Reply
| Previous | Next ]
C# Source Code
Marshalling data across threads to update UI components
By:
Andrew Baker
Email (spam proof):
Email the originator of this post
Date:
Wednesday, August 11, 2004
Hits:
2849
Category:
Threading/Asynchronous operations
Article:
Windows Forms controls are not thread safe. This means that you can only talk to the controls on the thread on which they were created. If you want to update a control from a non UI thread, then the call must be marshalled to the thread that created the control. There are five functions on a control that are safe to call from any thread: InvokeRequired, Invoke, BeginInvoke, EndInvoke and CreateGraphics. For all other method calls, you should use one of the invoke methods. To make a cross-thread call you need to call one of the Control.Invoke methods which takes a reference to a delegate. Below are two examples showing how to update a control using shared data and using a parameterised delegate. using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using System.Threading; using System.Diagnostics; namespace VBusers.Forms.Threading { ///
/// Example project demonstration two methods of marshalling data across threads to UI controls. ///
public class frmControlThreadTest : System.Windows.Forms.Form { // Message passed to UI thread private volatile string message = string.Empty; // A parameterised alternative to MethodInvoker delegate private delegate void MethodInvokerParam(object message); const int THREAD_WAIT = 100; private System.Windows.Forms.TextBox txtThreadResult; private System.Windows.Forms.Label lblThreadResult; private System.Windows.Forms.Button btnCreateThread; ///
/// Required designer variable. ///
private System.ComponentModel.Container components = null; public frmControlThreadTest() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // } ///
/// Clean up any resources being used. ///
protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code ///
/// Required method for Designer support - do not modify /// the contents of this method with the code editor. ///
private void InitializeComponent() { this.btnCreateThread = new System.Windows.Forms.Button(); this.txtThreadResult = new System.Windows.Forms.TextBox(); this.lblThreadResult = new System.Windows.Forms.Label(); this.SuspendLayout(); // // btnCreateThread // this.btnCreateThread.Location = new System.Drawing.Point(172, 41); this.btnCreateThread.Name = "btnCreateThread"; this.btnCreateThread.Size = new System.Drawing.Size(108, 23); this.btnCreateThread.TabIndex = 0; this.btnCreateThread.Text = "Create Thread"; this.btnCreateThread.Click += new System.EventHandler(this.btnCreateThread_Click); // // txtThreadResult // this.txtThreadResult.Location = new System.Drawing.Point(146, 12); this.txtThreadResult.Name = "txtThreadResult"; this.txtThreadResult.Size = new System.Drawing.Size(134, 20); this.txtThreadResult.TabIndex = 1; this.txtThreadResult.Text = ""; // // lblThreadResult // this.lblThreadResult.Location = new System.Drawing.Point(28, 12); this.lblThreadResult.Name = "lblThreadResult"; this.lblThreadResult.Size = new System.Drawing.Size(100, 16); this.lblThreadResult.TabIndex = 2; this.lblThreadResult.Text = "Thread Result:"; // // frmControlThreadTest // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(292, 73); this.Controls.Add(this.lblThreadResult); this.Controls.Add(this.txtThreadResult); this.Controls.Add(this.btnCreateThread); this.Name = "frmControlThreadTest"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Update Controls across Threads"; this.ResumeLayout(false); } #endregion ///
/// The main entry point for the application. ///
[STAThread] static void Main() { Application.Run(new frmControlThreadTest()); } private void btnCreateThread_Click(object sender, System.EventArgs e) { // Start a background thread Thread workerThread = new Thread(new ThreadStart(BackgroundTask)); workerThread.IsBackground = true; workerThread.Start(); } private void BackgroundTask() { try { Debug.WriteLine("Started Worker Thread on " + System.AppDomain.GetCurrentThreadId().ToString()); Thread.Sleep(1000); // Method 1 - use a .NET method invoker and update a shared variable MethodInvoker del = new MethodInvoker(this.UpdateControlUsingSharedData); for ( int index=0; index < 5; index++ ) { // Update the shared variable message = (index + 1).ToString(); // Invoke the delegate this.BeginInvoke(del); // Sleep Thread.Sleep(100); } // Clear message message = string.Empty; // Method 2 - use a custom delegate to pass a parameter to the delegate target. Thread.Sleep(1000); MethodInvokerParam customDel = new MethodInvokerParam(this.UpdateControlUsingParameters); string privateMessage; for ( int index=0; index < 5; index++ ) { // Update the shared variable privateMessage = (index + 1).ToString(); // Asynchronously invoke the delegate this.BeginInvoke(customDel, new object[]{privateMessage}); // Sleep Thread.Sleep(THREAD_WAIT); } } catch (ThreadInterruptedException ex) { // Thrown when the thread is interupted by another thread (eg. Thread.Abort is called) Debug.WriteLine(ex.ToString()); } catch (Exception ex) { // General exception Debug.WriteLine(ex.ToString()); } } // Invoked using the MethodInvoker, reads shared data. // Main prob. is the shared data can be altered by any other thread // so we may not get the value we expect if multiple threads access this // shared date. // As we have asychrously called the delegate which executes this method // you will also miss updates if you uncomment out the sleep at the bottom // of this routine. private void UpdateControlUsingSharedData() { Debug.WriteLine("Received: " + message + " on thread " + System.AppDomain.GetCurrentThreadId().ToString()); if ( txtThreadResult.InvokeRequired == true ) { // InvokeRequired returns True if the thread that called the routine // is on a different thread to the control Debug.Assert(false, "You cannot update a control directly from another thread!!!"); return; } txtThreadResult.Text = message; txtThreadResult.Refresh(); // Uncomment this and you will miss updates as the thread thats calling the // delegate will be updating the value while we are asleep. // Thread.Sleep(THREAD_WAIT * 2); } // Invoked using the MethodInvokerParam, uses parametersshared data. private void UpdateControlUsingParameters(object message) { Debug.WriteLine("Received: " + message + " on thread " + System.AppDomain.GetCurrentThreadId().ToString()); if ( txtThreadResult.InvokeRequired == true ) { // InvokeRequired returns True if the thread that called the routine // is on a different thread to the control Debug.Assert(false, "You cannot update a control directly from another thread!!!"); return; } txtThreadResult.Text = message.ToString(); txtThreadResult.Refresh(); // Uncomment this and you will NOT any miss updates since we // are using a byval parameter in the updates. Thread.Sleep(THREAD_WAIT * 2); } } }
Terms and Conditions
Support this site
Download a trial version of the best FTP application on the internet