[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 397
  • Last Modified:

How does the delegate know which thread to run the method on?

The following sample program creates a form with a textbox and a button.
When the button is pressed, it writes "Written by the main thread" to the textbox
starts a second thread,
the second thread waits 2 seconds, then writes "Written by the background thread" to the textbox via:

line 50:    string text = "Written by the background thread.";
line 56:    SetTextCallback d = new SetTextCallback(SetText);
line 57:    this.Invoke (d, new object[] { text + " (Invoke)" });

Questions:
1. How does the delegate know which thread to run the SetText method on?

2. on line 57, Is the new object[] part necessary?
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace CrossThreadDemo
{
    public class Form1 : Form
    {
        // This delegate enables asynchronous calls for setting
        // the text property on a TextBox control.
        delegate void SetTextCallback(string text);

        // This thread is used to demonstrate thread-safe way
        // to call a Windows Forms control.
        private Thread demoThread = null;
        private TextBox textBox1;
        private Button setTextSafeBtn;

        public Form1()
        {
            InitializeComponent();
        }

        // This event handler creates a thread that calls a
        // Windows Forms control in a thread-safe way.
        private void setTextSafeBtn_Click(
            object sender,
            EventArgs e)
        {
            // Create a background thread and start it.
            this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
            this.demoThread.Start();

            // Continue in the main thread.  Set a textbox value
            // that will be overwritten by demoThread.
            textBox1.Text = "Written by the main thread.";
        }

        // If the calling thread is different from the thread that
        // created the TextBox control, this method passes in the
        // the SetText method to the SetTextCallback delegate and
        // passes in the delegate to the Invoke method.
        private void ThreadProcSafe()
        {
            // Wait two seconds to simulate some background work
            // being done.
            Thread.Sleep(2000);

            string text = "Written by the background thread.";
            // Check if this method is running on a different thread
            // than the thread that created the control.
            if (this.textBox1.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke (d, new object[] { text + " (Invoke)" });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.textBox1.Text = text + " (No Invoke)";
            }
        }

        // This method is passed in to the SetTextCallBack delegate
        // to set the Text property of textBox1.
        private void SetText(string text)
        {
            this.textBox1.Text = text;
        }


        #region Windows Form Designer generated code
        private void InitializeComponent()
        {
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.setTextSafeBtn = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(12, 12);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(360, 20);
            this.textBox1.TabIndex = 0;
            //
            // setTextSafeBtn
            //
            this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
            this.setTextSafeBtn.Name = "setTextSafeBtn";
            this.setTextSafeBtn.TabIndex = 2;
            this.setTextSafeBtn.Text = "Safe Call";
            this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
            //
            // Form1
            //
            this.ClientSize = new System.Drawing.Size(388, 96);
            this.Controls.Add(this.setTextSafeBtn);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();
        }
        #endregion

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new Form1());
        }
    }
}

Open in new window

0
deleyd
Asked:
deleyd
  • 3
  • 2
  • 2
  • +1
5 Solutions
 
saraganiCommented:
The thread doesn't know...
actually, this.Invoke knows.

this.Invoke is a function that runs delegates on the UI thread.

yes, it is necessary.

In order to pass parameters to the function you need to give it params as an array of objects
0
 
dimajCommented:
1) When delegates are executed, they get executed in their own thread. By default you have a UI thread, which is referred to as a Main Thread in your example.

2) Yes, Object[] is required since function SetText takes a parameter. If your function didn't take any parameters, Object[] would be optional. Likewise, if SetText would have 2 parameters, Object[] would have 2 elements.

You can read up more on this here: http://msdn.microsoft.com/en-us/library/30s4t80c.aspx

Hope this helps.

dimaj
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
You should do the InvokeRequired() check INSIDE your SetText() method so that it will work no matter which thread it is called from.  The "this" refers to the form itself.  Invoke() runs the delegate on the same thread that created the control being referenced.  If it makes your brain happier you can Invoke() against the TextBox instead:
private void ThreadProcSafe()
        {
            // Wait two seconds to simulate some background work
            // being done.
            Thread.Sleep(2000);

            string text = "Written by the background thread.";
            SetText(text);
        }

        // This method is passed in to the SetTextCallBack delegate
        // to set the Text property of textBox1.
        private void SetText(string text)
        {
            // Check if this method is running on a different thread
            // than the thread that created the control.
            if (this.textBox1.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText);
                this.textBox1.Invoke (d, new object[] { text });
            }
            else
            {
                // It's on the main UI thread, no need for Invoke
                this.textBox1.Text = text;
            }
        }

Open in new window

0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
deleydAuthor Commented:
Interesting idea to put the InvokeRequired() check INSIDE the SetText() method. SetText() calls itself.

Why does it still work if I remove the new object[] part and just pass text? Why is this bad to do?
this.Invoke(d, text + " (Invoke)" );

Open in new window

It seems to compile and run without complaint.
0
 
Mike TomlinsonMiddle School Assistant TeacherCommented:
Not sure why it works that way.  The documentation calls for an array of Object so that is always what I've done.  =)
0
 
saraganiCommented:
It still works since your function requires only 1 parameter, and in this case it is not a bad thing to do.

However, I don't like the idea of having hundreds of functions for updating the UI.
Now you have SetText which updates the text of textbox1.
In the future you will have several textboxes so you will need a function for each of them, or have a a generic function that accepts a TexBox and a Text..

But you will also need to update a Label... so another function and another type of Delegate (One that accepts a Label and a Text).

You will want to update a Combo.... yet another function and another delegate.
You will want to update a progress bar, now you will need a delegate and a function that takes a progress bar and a value. You will also need a function and delegate that takes a progress bar and 2 values for setting the Min and Max values of it.


Do you see where it is going??


I suggest here a better and easier solution using Anonymous Method...

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace CrossThreadDemo
{
    public class Form1 : Form
    {
        // This thread is used to demonstrate thread-safe way
        // to call a Windows Forms control.
        private Thread demoThread = null;
        private TextBox textBox1;
        private Button setTextSafeBtn;

        public Form1()
        {
            InitializeComponent();
        }

        // This event handler creates a thread that calls a
        // Windows Forms control in a thread-safe way.
        private void setTextSafeBtn_Click(
            object sender,
            EventArgs e)
        {
            // Create a background thread and start it.
            this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
            this.demoThread.Start();

            // Continue in the main thread.  Set a textbox value
            // that will be overwritten by demoThread.
            textBox1.Text = "Written by the main thread.";
        }

        // If the calling thread is different from the thread that
        // created the TextBox control, this method passes in the
        // the SetText method to the SetTextCallback delegate and
        // passes in the delegate to the Invoke method.
        private void ThreadProcSafe()
        {
            // Wait two seconds to simulate some background work
            // being done.
            Thread.Sleep(2000);

            MethodInvoker mi = delegate()
            {
               string text = "Written by the background thread.";
               // Check if this method is running on a different thread
               // than the thread that created the control.
              this.textBox1.Text = text;
            };
            updateUI(mi);
        }

        private void updateUI(MethodInvoker mi)
        {
           if (this.InvokeRequired)
           {
              this.BeginInvoke(mi);
           }
           else
           {
               mi();
           }
       }

        #region Windows Form Designer generated code
        private void InitializeComponent()
        {
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.setTextSafeBtn = new System.Windows.Forms.Button();
            this.SuspendLayout();
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(12, 12);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(360, 20);
            this.textBox1.TabIndex = 0;
            //
            // setTextSafeBtn
            //
            this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
            this.setTextSafeBtn.Name = "setTextSafeBtn";
            this.setTextSafeBtn.TabIndex = 2;
            this.setTextSafeBtn.Text = "Safe Call";
            this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
            //
            // Form1
            //
            this.ClientSize = new System.Drawing.Size(388, 96);
            this.Controls.Add(this.setTextSafeBtn);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
            this.PerformLayout();
        }
        #endregion

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new Form1());
        }
    }
} 

Open in new window

0
 
deleydAuthor Commented:
Super. I'm going to follow-up with another question which I'll start a new post and add comment here pointing to it.
0
 
deleydAuthor Commented:
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

  • 3
  • 2
  • 2
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now