Hello everyone,
Recently I was asked in class how to solve the whole “Unable to access control created on another thread” exception.
The teachers apperantly didn’t know the solution so my classmates turned to me.
It’s pretty simple actually.
What we basicly have to do, is delegate the task.
Let’s say we have 2 threads.
The main thread being our application when it starts, and a worker thread that’s created when we press a button.
When the worker thread has been started, we want it to update a TextBox value and increment it by 1.
Now, if we do not delegate this task to the main thread when it’s being called from the worker thread, it will raise an exception saying it can’t access controls created by / on a different thread.
So the only thing we have to do, is create a delegate for this:
VB.NET:
'Delegate Private Delegate Sub MyDelegate()
How to declare a delegate in VB.NET
C#.NET
// Delegate public delegate void MyDelegate();
How to declare a delegate in C#.NET
That’s about it when it comes down to actually creating the delegates.
Now it’s time to use them.
So we will create a new thread when we click a button, and that thread will execute a method called “DoIncrement()”
This method will loop untill our counter is 10 and it will sleep for 1000 miliseconds (= 1 second) after it has incremented the value.
Once the counter has been incremented, a method called “ShowValue()” will be called to display the current value in a TextBox
VB.NET:
Private Sub DoIncrement() While (Me.m_nCounter < 10) 'Increment the counter Me.m_nCounter += 1 Me.ShowValue() 'Let the worker thread sleep Thread.Sleep(1000) End While Me.m_nCounter = 0 MessageBox.Show("Worker thread is done!") End Sub
The DoIncrement() procedure in VB.NET
C#.NET
/// /// Increments the counter by 1 /// private void DoIncrement() { while (m_nCounter < 10) { // Increment the counter this.m_nCounter++; this.ShowValue(); // Pause the worker thread for 1 second Thread.Sleep(1000); } this.m_nCounter = 0; MessageBox.Show("Worker thread has finished!"); }
The DoIncrement() procedure in C#.NET
Now, in the “ShowValue()” method we will call upon the delegate we’ve declared.
What we’ll do is check if the TextBox we’re trying to show the value in belongs to another thread and if it does, delegate the task “ShowValue” to the thread that owns the control.
VB.NET:
Private Sub ShowValue() If (Me.txtValue.InvokeRequired) Then 'Declare a new instance of our delegate Dim Show As MyDelegate 'Setup the delegate Show = AddressOf ShowValue 'Invoke the delegate for the control Me.txtValue.Invoke(Show) Else 'Update the value of the textbox txtValue.Text = m_nCounter.ToString() End If End Sub
The ShowValue() procedure in VB.NET
C#.NET:
/// /// Shows the current value of m_nCounter in txtValue /// private void ShowValue() { // Check to which thread the control belongs if (this.txtValue.InvokeRequired) { // Fire the delegate this.txtValue.Invoke(new MyDelegate(ShowValue)); } else { // Show the current value this.txtValue.Text = this.m_nCounter.ToString(); } }
The ShowValue() procedure in C#.NET
Here is the complete source code for the VB.NET and C#.NET projects.
All you need is a small form with 1 button called btnStart and a TextBox called txtValue
VB.NET
Imports System.Threading Public Class Form1 'Delegate Private Delegate Sub MyDelegate() 'Variables Private m_nCounter As Integer Private m_trdCounter As Thread ''' ''' Occurs when the user clicks the start button ''' ''' System.Object : The object calling this procedure ''' System.EventArgs : The event arguments ''' Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click 'Setup the thread Me.m_trdCounter = New Thread(AddressOf Me.DoIncrement) Me.m_trdCounter.IsBackground = True 'Start the new thread Me.m_trdCounter.Start() End Sub ''' ''' Increment the value of m_nCounter by 1 and call ShowValue() ''' ''' Private Sub DoIncrement() While (Me.m_nCounter < 10) 'Increment the counter Me.m_nCounter += 1 Me.ShowValue() 'Let the worker thread sleep Thread.Sleep(1000) End While Me.m_nCounter = 0 MessageBox.Show("Worker thread is done!") End Sub ''' ''' Show the current value of m_nCounter in the TextBox ''' ''' Private Sub ShowValue() If (Me.txtValue.InvokeRequired) Then 'Declare a new instance of our delegate Dim Show As MyDelegate 'Setup the delegate Show = AddressOf ShowValue 'Invoke the delegate for the control Me.txtValue.Invoke(Show) Else 'Update the value of the textbox txtValue.Text = m_nCounter.ToString() End If End Sub End Class
The source code in VB.NET
C#.NET
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; namespace Crossthreading_Example { // Delegate public delegate void MyDelegate(); public partial class Form1 : Form { // Variables private int m_nCounter; private Thread m_trdCounter; /// /// Default constructor /// public Form1() { InitializeComponent(); // Initialize m_nCounter = 0; } /// /// Occurs when the user clicks the start button /// /// System.Object : The object calling this method /// System.EventArgs : The event arguments private void btnStart_Click(object sender, EventArgs e) { // Setup a new thread this.m_trdCounter = new Thread(DoIncrement); this.m_trdCounter.IsBackground = true; // Start the thread this.m_trdCounter.Start(); } /// /// Increments the counter by 1 /// private void DoIncrement() { while (m_nCounter < 10) { // Increment the counter this.m_nCounter++; this.ShowValue(); // Pause the worker thread for 1 second Thread.Sleep(1000); } this.m_nCounter = 0; MessageBox.Show("Worker thread has finished!"); } /// /// Shows the current value of m_nCounter in txtValue /// private void ShowValue() { // Check to which thread the control belongs if (this.txtValue.InvokeRequired) { // Fire the delegate this.txtValue.Invoke(new MyDelegate(ShowValue)); } else { // Show the current value this.txtValue.Text = this.m_nCounter.ToString(); } } } }
The source code in C#
There you go, access controls from other threads ;P
If you have any questions regarding this topic, feel free to ask and I’ll do my best to answer them.
Happy coding!
– Dirk
I don’t see the point in creating a new Delegete for those small tasks.
You can just use the lambda (o, e) => { } ; syntax for that.
Simplicity for the win.
What about accessing data accross threads? I would like to access a dataset object from another thread. Meaning I click a button that runs some long running data query in the background and when it finally completes I would like to pass or grant access to this dataset on the main thread. Can you tell me how to do this?
This could be accomplished by using the BackGroundWorker in .NET
When a BGW has finished working it allows you to return the data to the main thread without much hassle. Without any more info on how you’re planning on doing it I can’t really help you much more 😉
dear dirk, can you please let me know how I can do it in asp.net webform. my textbox is not showing updated values except very first.