C # Updating TextBox from multiple threads
I put together a simple WinForm that would spawn a series of threads to loop from 0 to 10000 - the goal of this is to slow down Windows so that some other programs run slowly.
Basically the form has a textbox in which I want to write the loop index from each thread. It was ok for one thread, but since I presented more threads, I would hang the app when I hit the Stop button - I'm not too sure where to go from here.
My example is probably poorly written. I want to better understand multithreading, deadlocks, etc. I've done BackgroundWorker before, but have been doing Java for most of the past two plus years.
Form1.cs
public delegate void SetTextDelegate(string text);
public partial class Form1 : Form
{
private Thread[] _slow;
private object lockTextBox = new object();
public Form1()
{
InitializeComponent();
}
#region Event Handlers
private void ui_btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void ui_btnStart_Click(object sender, EventArgs e)
{
if (_slow != null)
{
StopAllThreads();
}
_slow = new Thread[ (int) numNoOfTheads.Value ];
for( int i = 0; i < numNoOfTheads.Value; i++)
{
_slow[i] = new Thread(ThreadRunLoop);
_slow[i].Start();
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (_slow != null)
{
StopAllThreads();
}
}
private void ui_btnStop_Click(object sender, EventArgs e)
{
if (_slow != null)
{
StopAllThreads();
}
}
private void ui_btnClear_Click(object sender, EventArgs e)
{
this.textBox1.Clear();
}
#endregion
protected void ThreadRunLoop()
{
try
{
for (int i = 0; i < 10000; i++)
{
UpdateText("Loop " + i + " for " + Thread.CurrentThread.ManagedThreadId);
}
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine("Thread has been interrupted.");
}
}
private void UpdateText(string text)
{
//lock (lockTextBox)
//{
if (textBox1.InvokeRequired)
{
textBox1.Invoke(new SetTextDelegate(UpdateText), text);
}
else
{
textBox1.SuspendLayout();
textBox1.Text = textBox1.Text + text + System.Environment.NewLine;
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox1.ResumeLayout();
}
//}
}
private void StopAllThreads()
{
for (int i = 0; i < _slow.Length; i++)
{
if (_slow[i] != null)
{
_slow[i].Interrupt();
_slow[i] = null;
}
}
_slow = null;
}
}
Form1.Designer.cs
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (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.ui_btnClose = new System.Windows.Forms.Button();
this.ui_btnStart = new System.Windows.Forms.Button();
this.ui_btnStop = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.ui_btnClear = new System.Windows.Forms.Button();
this.numNoOfTheads = new System.Windows.Forms.NumericUpDown();
this.label1 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.numNoOfTheads)).BeginInit();
this.SuspendLayout();
//
// ui_btnClose
//
this.ui_btnClose.Location = new System.Drawing.Point(433, 268);
this.ui_btnClose.Name = "ui_btnClose";
this.ui_btnClose.Size = new System.Drawing.Size(75, 23);
this.ui_btnClose.TabIndex = 0;
this.ui_btnClose.Text = "Close";
this.ui_btnClose.UseVisualStyleBackColor = true;
this.ui_btnClose.Click += new System.EventHandler(this.ui_btnClose_Click);
//
// ui_btnStart
//
this.ui_btnStart.Location = new System.Drawing.Point(12, 12);
this.ui_btnStart.Name = "ui_btnStart";
this.ui_btnStart.Size = new System.Drawing.Size(75, 23);
this.ui_btnStart.TabIndex = 1;
this.ui_btnStart.Text = "Start";
this.ui_btnStart.UseVisualStyleBackColor = true;
this.ui_btnStart.Click += new System.EventHandler(this.ui_btnStart_Click);
//
// ui_btnStop
//
this.ui_btnStop.Location = new System.Drawing.Point(12, 41);
this.ui_btnStop.Name = "ui_btnStop";
this.ui_btnStop.Size = new System.Drawing.Size(75, 23);
this.ui_btnStop.TabIndex = 2;
this.ui_btnStop.Text = "Stop";
this.ui_btnStop.UseVisualStyleBackColor = true;
this.ui_btnStop.Click += new System.EventHandler(this.ui_btnStop_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(93, 12);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBox1.Size = new System.Drawing.Size(415, 241);
this.textBox1.TabIndex = 3;
//
// ui_btnClear
//
this.ui_btnClear.Location = new System.Drawing.Point(352, 268);
this.ui_btnClear.Name = "ui_btnClear";
this.ui_btnClear.Size = new System.Drawing.Size(75, 23);
this.ui_btnClear.TabIndex = 4;
this.ui_btnClear.Text = "Clear";
this.ui_btnClear.UseVisualStyleBackColor = true;
this.ui_btnClear.Click += new System.EventHandler(this.ui_btnClear_Click);
//
// numNoOfTheads
//
this.numNoOfTheads.Location = new System.Drawing.Point(12, 98);
this.numNoOfTheads.Name = "numNoOfTheads";
this.numNoOfTheads.Size = new System.Drawing.Size(74, 20);
this.numNoOfTheads.TabIndex = 5;
this.numNoOfTheads.Value = new decimal(new int[] {
1,
0,
0,
0});
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(9, 82);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(83, 13);
this.label1.TabIndex = 6;
this.label1.Text = "No. Of Threads:";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(520, 303);
this.Controls.Add(this.label1);
this.Controls.Add(this.numNoOfTheads);
this.Controls.Add(this.ui_btnClear);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.ui_btnStop);
this.Controls.Add(this.ui_btnStart);
this.Controls.Add(this.ui_btnClose);
this.Name = "Form1";
this.Text = "Slow My Machine";
this.Load += new System.EventHandler(this.Form1_Load);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
((System.ComponentModel.ISupportInitialize)(this.numNoOfTheads)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button ui_btnClose;
private System.Windows.Forms.Button ui_btnStart;
private System.Windows.Forms.Button ui_btnStop;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button ui_btnClear;
private System.Windows.Forms.NumericUpDown numNoOfTheads;
private System.Windows.Forms.Label label1;
}
Update If I move the lock to else in the UpdateText method and add Thread.Sleep (20); in a loop, then my GUI is more responsive and I can hit the Stop button and move the form around.
Any feedback, corrections would be appreciated.
source to share
lock
inside UpdateText
will lead to a dead end. The worker thread acquires the lock and then calls Invoke
. The UI thread then makes attempts to acquire the same lock, but has to wait for it to be released. The point is that the lock will never be released, because it will Invoke
block until the UI thread has finished executing the delegate. This never happens because the UI thread is still waiting to acquire the lock. Dead end!
source to share
Change the for loop to
for (int i = 0; i < 10000; i++)
{
var text = "Loop " + i + " for " + Thread.CurrentThread.ManagedThreadId;
if (textBox1.InvokeRequired)
textBox1.Invoke(new SetTextDelegate(UpdateText), text);
else
UpdateText(text);
}
And change UpdateText to
private void UpdateText(string text)
{
textBox1.SuspendLayout();
textBox1.Text = textBox1.Text + text + System.Environment.NewLine;
textBox1.SelectionStart = textBox1.Text.Length;
textBox1.ScrollToCaret();
textBox1.ResumeLayout();
}
EDIT : my mistake. It will just improve the organization, not in any aspect. If you want to update the interface frequently, you should use the BackgroundWorker , which is what rdkleine said.
source to share
Use BackgroundWorker, which updates the UI thread.
Here's a good example of using BackgroundWorker:
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
It doesn't explain how to fetch data (int value) and put it in a textbox, but it's a good start.
source to share