Multithreading and Creating new controls

G

Guest

I'm trying to dynamically create a set controls in a Threadpool and then add them to thte Controls collection of a Form (the main thread). The controls seem to be created in the thread an get passed back successfully via an derived EventArgs class, but I get "invalid handle" error messages when trying to work them back on the main thread. I've seen a number of articles about a control being "owned" by the thread that created it, so, my question is, is what I'm trying to do even possible? Is there some other way I should go about it?
 
J

Jon Skeet [C# MVP]

Steve said:
I'm trying to dynamically create a set controls in a Threadpool and
then add them to thte Controls collection of a Form (the main
thread). The controls seem to be created in the thread an get passed
back successfully via an derived EventArgs class, but I get "invalid
handle" error messages when trying to work them back on the main
thread. I've seen a number of articles about a control being "owned"
by the thread that created it, so, my question is, is what I'm
tryingto do even possible? Is there some other way I should go about
it?

You should create them on the main thread, definitely. Do your controls
take a long time to construct?
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Hi Steve,

In Windows a window cannot have children created in different thread than
the parent istelf.

What you wanna do is in the thread pool call parent's Invoke method and pass
a delegate to the control creating routine. This will execute the creator in
the UI thread and then you can safely add those controls to the parent.
However you may not have a reference to the control which Invoke you need to
call (for example you may have more then one UI threads).
To solve this problem don't forget that it is not important, which thread
has been created the .NET control class it is important which thread has
executed the code that causes control's Handle to be created. So to solve
the prblem waht you need to do is when reveive the event with the created
control (the event hadnler is executed from the worker thread) to switch to
the UI thread (assuming that the event consumer is a control) and recreate
the control handler. Controls have Recreate method, but it is protected
unfortunately. You can use reflection to call that method or if the controls
are user controls you can expose a method for that.


The following is an example that do exacly that

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.Reflection;

namespace ThreadingAndControls
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(120, 224);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
ControlCreator cc = new ControlCreator();
cc.ControlCreated +=new ControlEventHandler(cc_ControlCreated);
//Starting the creation method in a worker thread form the thread pool
ThreadPool.QueueUserWorkItem(new WaitCallback(cc.DoCreation));

}

private void cc_ControlCreated(object sender, ControlEventArgs e)
{
//This method is executed by the worker thread.

e.Control.Text = "TestButton";
e.Control.Location = new Point(10,10);

//Executes the RecreateControl method in the UI thread.
e.Control.CreateControl();
this.Invoke(new ControlEventHandler(RecreateControl), new
object[2]{sender, e});


}

void RecreateControl(object sender, ControlEventArgs e)
{
PropertyInfo pi = typeof(Control).GetProperty("CreateThreadId",
BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine("Form created by TID: {0}", pi.GetValue(this, new
object[0]));
pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl created by TID: {0}", pi.GetValue(e.Control, new
object[0]));

MethodInfo mi = e.Control.GetType().GetMethod("RecreateHandle",
BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(e.Control, new object[0]);

pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl re-created by TID: {0}", pi.GetValue(e.Control,
new object[0]));

this.Controls.Add(e.Control);



}






}

class ControlCreator
{
public event ControlEventHandler ControlCreated;

public void DoCreation(object state)
{
Button btn = new Button();
Application.DoEvents();
ControlCreated(this, new ControlEventArgs(btn));

}
}

}
--

Stoitcho Goutsev (100) [C# MVP]


Steve said:
I'm trying to dynamically create a set controls in a Threadpool and then
add them to thte Controls collection of a Form (the main thread). The
controls seem to be created in the thread an get passed back successfully
via an derived EventArgs class, but I get "invalid handle" error messages
when trying to work them back on the main thread. I've seen a number of
articles about a control being "owned" by the thread that created it, so, my
question is, is what I'm trying to do even possible? Is there some other
way I should go about it?
 
J

Jon Skeet [C# MVP]

Steve said:
My UI is defined dynamically and I gather the control definitions
from multiple datasources (databases). The client software may be
running on some older machines (less processor and RAM), so I was
just attempting to tune the app startup a little. My thought was to
outsource the database connection and control creation to a
multithreaded process, but I can manage it a little differently.

Well, you could create one set of controls on one thread and one on a
different thread (I think) but you shouldn't create one set on one
thread and then use them from another thread.

I would create all the dynamic stuff (and any container forms etc, to
keep all the controls belonging to the same window on the same thread
for sanity's sake) on one thread, and keep your "controller UI" (or
whatever) on another thread. I believe that should work fine.
 
G

Guest

Thank you very much for your input Stoitcho. I had actually just completed a test implementation of your first suggestion (delegate to the parent's control creation routine) when I read your message. Your second suggestion is an interesting implementation. Between the two, would it be better/more efficient to pass a delegate or to recreate the control handles after-the-fact (I’ll be creating hundreds of controls)? Of course I know that incorporating reflection has a certain cost, but as it happens the top-most control I’m creating is inherits UserControl, so I could always expose the RecreateHandle method. Also, I haven’t gotten quite this far yet, but if I have many threads off creating controls, will I need to Monitor.Enter/Exit the Form.Controls collection? From what I understand, I don’t think I’ll need to, but a friend suggested I might.

Again, thank you for your help.
Steve


Stoitcho Goutsev (100) said:
Hi Steve,

In Windows a window cannot have children created in different thread than
the parent istelf.

What you wanna do is in the thread pool call parent's Invoke method and pass
a delegate to the control creating routine. This will execute the creator in
the UI thread and then you can safely add those controls to the parent.
However you may not have a reference to the control which Invoke you need to
call (for example you may have more then one UI threads).
To solve this problem don't forget that it is not important, which thread
has been created the .NET control class it is important which thread has
executed the code that causes control's Handle to be created. So to solve
the prblem waht you need to do is when reveive the event with the created
control (the event hadnler is executed from the worker thread) to switch to
the UI thread (assuming that the event consumer is a control) and recreate
the control handler. Controls have Recreate method, but it is protected
unfortunately. You can use reflection to call that method or if the controls
are user controls you can expose a method for that.


The following is an example that do exacly that

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.Reflection;

namespace ThreadingAndControls
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(120, 224);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
ControlCreator cc = new ControlCreator();
cc.ControlCreated +=new ControlEventHandler(cc_ControlCreated);
//Starting the creation method in a worker thread form the thread pool
ThreadPool.QueueUserWorkItem(new WaitCallback(cc.DoCreation));

}

private void cc_ControlCreated(object sender, ControlEventArgs e)
{
//This method is executed by the worker thread.

e.Control.Text = "TestButton";
e.Control.Location = new Point(10,10);

//Executes the RecreateControl method in the UI thread.
e.Control.CreateControl();
this.Invoke(new ControlEventHandler(RecreateControl), new
object[2]{sender, e});


}

void RecreateControl(object sender, ControlEventArgs e)
{
PropertyInfo pi = typeof(Control).GetProperty("CreateThreadId",
BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine("Form created by TID: {0}", pi.GetValue(this, new
object[0]));
pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl created by TID: {0}", pi.GetValue(e.Control, new
object[0]));

MethodInfo mi = e.Control.GetType().GetMethod("RecreateHandle",
BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(e.Control, new object[0]);

pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl re-created by TID: {0}", pi.GetValue(e.Control,
new object[0]));

this.Controls.Add(e.Control);



}






}

class ControlCreator
{
public event ControlEventHandler ControlCreated;

public void DoCreation(object state)
{
Button btn = new Button();
Application.DoEvents();
ControlCreated(this, new ControlEventArgs(btn));

}
}

}
--

Stoitcho Goutsev (100) [C# MVP]


Steve said:
I'm trying to dynamically create a set controls in a Threadpool and then
add them to thte Controls collection of a Form (the main thread). The
controls seem to be created in the thread an get passed back successfully
via an derived EventArgs class, but I get "invalid handle" error messages
when trying to work them back on the main thread. I've seen a number of
articles about a control being "owned" by the thread that created it, so, my
question is, is what I'm trying to do even possible? Is there some other
way I should go about it?
 
S

Stoitcho Goutsev \(100\) [C# MVP]

Hi Steve,

The thing is that you have to call recreate handle in the UI thread. This is
done just because the both handlers the parent (which is created by the UI
thread) and the child (which initially may be created by a worker thread)
have to be created by the same thread. When I was playing with the the code
I posted I found that it has to be added by the UI thread as well. (I have
no explanation of this)Because all recreate handlers are execured and
controls added by the same thread (the UI thread) you don't need to guard
Form.Control's collection.

--
HTH
Stoitcho Goutsev (100) [C# MVP]


Steve said:
Thank you very much for your input Stoitcho. I had actually just
completed a test implementation of your first suggestion (delegate to the
parent's control creation routine) when I read your message. Your second
suggestion is an interesting implementation. Between the two, would it be
better/more efficient to pass a delegate or to recreate the control handles
after-the-fact (I'll be creating hundreds of controls)? Of course I know
that incorporating reflection has a certain cost, but as it happens the
top-most control I'm creating is inherits UserControl, so I could always
expose the RecreateHandle method. Also, I haven't gotten quite this far
yet, but if I have many threads off creating controls, will I need to
Monitor.Enter/Exit the Form.Controls collection? From what I understand, I
don't think I'll need to, but a friend suggested I might.
Again, thank you for your help.
Steve


Stoitcho Goutsev (100) said:
Hi Steve,

In Windows a window cannot have children created in different thread than
the parent istelf.

What you wanna do is in the thread pool call parent's Invoke method and pass
a delegate to the control creating routine. This will execute the creator in
the UI thread and then you can safely add those controls to the parent.
However you may not have a reference to the control which Invoke you need to
call (for example you may have more then one UI threads).
To solve this problem don't forget that it is not important, which thread
has been created the .NET control class it is important which thread has
executed the code that causes control's Handle to be created. So to solve
the prblem waht you need to do is when reveive the event with the created
control (the event hadnler is executed from the worker thread) to switch to
the UI thread (assuming that the event consumer is a control) and recreate
the control handler. Controls have Recreate method, but it is protected
unfortunately. You can use reflection to call that method or if the controls
are user controls you can expose a method for that.


The following is an example that do exacly that

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.Reflection;

namespace ThreadingAndControls
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Button button1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(120, 224);
this.button1.Name = "button1";
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}
#endregion

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

private void button1_Click(object sender, System.EventArgs e)
{
ControlCreator cc = new ControlCreator();
cc.ControlCreated +=new ControlEventHandler(cc_ControlCreated);
//Starting the creation method in a worker thread form the thread pool
ThreadPool.QueueUserWorkItem(new WaitCallback(cc.DoCreation));

}

private void cc_ControlCreated(object sender, ControlEventArgs e)
{
//This method is executed by the worker thread.

e.Control.Text = "TestButton";
e.Control.Location = new Point(10,10);

//Executes the RecreateControl method in the UI thread.
e.Control.CreateControl();
this.Invoke(new ControlEventHandler(RecreateControl), new
object[2]{sender, e});


}

void RecreateControl(object sender, ControlEventArgs e)
{
PropertyInfo pi = typeof(Control).GetProperty("CreateThreadId",
BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine("Form created by TID: {0}", pi.GetValue(this, new
object[0]));
pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl created by TID: {0}", pi.GetValue(e.Control, new
object[0]));

MethodInfo mi = e.Control.GetType().GetMethod("RecreateHandle",
BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(e.Control, new object[0]);

pi = typeof(Control).GetProperty("CreateThreadId", BindingFlags.Instance
| BindingFlags.NonPublic);
Console.WriteLine("Ctrl re-created by TID: {0}", pi.GetValue(e.Control,
new object[0]));

this.Controls.Add(e.Control);



}






}

class ControlCreator
{
public event ControlEventHandler ControlCreated;

public void DoCreation(object state)
{
Button btn = new Button();
Application.DoEvents();
ControlCreated(this, new ControlEventArgs(btn));

}
}

}
--

Stoitcho Goutsev (100) [C# MVP]


Steve said:
I'm trying to dynamically create a set controls in a Threadpool and
then
add them to thte Controls collection of a Form (the main thread). The
controls seem to be created in the thread an get passed back successfully
via an derived EventArgs class, but I get "invalid handle" error messages
when trying to work them back on the main thread. I've seen a number of
articles about a control being "owned" by the thread that created it, so, my
question is, is what I'm trying to do even possible? Is there some other
way I should go about it?
 

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