Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Updating TextBox from multiple Threads

I was putting together a simple WinForm that would spawn a number of threads to loop from 0 to 10000 - the purpose of this - to slow down Windows to make some other programs run slow.

Basically the form has a text box that I want to write the loop index to from each thread. All was fine for a single thread, but since I have introduced more threads, I would appear to hang the application when I click on the Stop button - I am not too sure where to go from here.

My sample is probably not written well. I want to get a better understanding of multithreadding, deadlocks, etc. I have dabbled a little with BackgroundWorker in the past, but have been doing Java for the majority of the last 2+ 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 the else in the UpdateText method and add a Thread.Sleep(20); in the loop, then my GUI is more responsive and I can click on the Stop button and move the form around.

Any feedback, fixes will be appreciated.

like image 488
Andez Avatar asked Mar 29 '12 10:03

Andez


1 Answers

The lock inside UpdateText will cause a deadlock. The worker thread acquires the lock and then calls Invoke. The UI thread then calls tries to acquire the same lock, but has to wait until it is released. The thing is that the lock will never get released because Invoke blocks until the UI thread has finished executing the delegate. That never happens because the UI thread is still waiting to acquire the lock. Deadlock!

like image 153
Brian Gideon Avatar answered Sep 30 '22 01:09

Brian Gideon