为什么在这个特定的WinForms应用程序中出现死锁?

时间:2013-10-27 02:39:59

标签: c# winforms deadlock

我创建了一个带有文本框的简单Windows窗体,以及“设置”按钮和“切换”按钮。单击切换按钮时,会创建一个线程,重复将文本设置到文本框。当我再次单击该按钮时,线程停止。单击“设置”按钮时,文本框将设置为文本框一次。如果我执行以下操作,则会发生死锁:

  1. 运行应用程序(在调试模式下)。
  2. 单击切换按钮,让文本在文本框中运行。
  3. 单击“设置”按钮。 - >此步骤发生死锁。
  4. 你能解释为什么以及在这种情况下如何发生死锁?如何避免呢?

    以下是代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;
    
    namespace DeadLockTest
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            [STAThread]
            static void Main()
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Windows.Forms;
    
    namespace DeadLockTest
    {
        public class Form1 : Form
        {
            private System.Windows.Forms.TextBox textBox1;
            private System.Windows.Forms.Button button1;
            private System.Windows.Forms.Button button2;
    
            private int counter;
            private Thread thread;
            private bool cancelRequested;
            private string content;
            private object lockKey = new object();
    
            public Form1()
            {
                InitializeComponent();
            }
    
            protected void UpdateContent()
            {
                this.textBox1.Text = this.content;
            }
    
            protected void InvokeUpdateContent()
            {
                lock (this.lockKey)
                {
                    if (InvokeRequired)
                    {
                        Invoke(new Action(UpdateContent));
                    }
                    else
                    {
                        UpdateContent();
                    }
                }
            }
    
            protected void SetText(string text)
            {
                this.content = text;
                InvokeUpdateContent();
            }
    
            protected void StressTest()
            {
                int localCounter = 0;
    
                while (!this.cancelRequested)
                {
                    SetText(string.Format("{0}", localCounter++));
                }
    
                this.cancelRequested = false;
                this.thread = null;
            }
    
            private void InitializeComponent()
            {
                this.textBox1 = new System.Windows.Forms.TextBox();
                this.button1 = new System.Windows.Forms.Button();
                this.button2 = new System.Windows.Forms.Button();
                this.SuspendLayout();
                // 
                // textBox1
                // 
                this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
                            | System.Windows.Forms.AnchorStyles.Right)));
                this.textBox1.Location = new System.Drawing.Point(12, 12);
                this.textBox1.Name = "textBox1";
                this.textBox1.ReadOnly = true;
                this.textBox1.Size = new System.Drawing.Size(260, 20);
                this.textBox1.TabIndex = 0;
                // 
                // button1
                // 
                this.button1.Location = new System.Drawing.Point(12, 38);
                this.button1.Name = "button1";
                this.button1.Size = new System.Drawing.Size(75, 23);
                this.button1.TabIndex = 1;
                this.button1.Text = "Set";
                this.button1.UseVisualStyleBackColor = true;
                this.button1.Click += new System.EventHandler(this.button1_Click);
                // 
                // button2
                // 
                this.button2.Location = new System.Drawing.Point(93, 38);
                this.button2.Name = "button2";
                this.button2.Size = new System.Drawing.Size(75, 23);
                this.button2.TabIndex = 2;
                this.button2.Text = "Toggle";
                this.button2.UseVisualStyleBackColor = true;
                this.button2.Click += new System.EventHandler(this.button2_Click);
                // 
                // Form1
                // 
                this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
                this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
                this.ClientSize = new System.Drawing.Size(284, 262);
                this.Controls.Add(this.button2);
                this.Controls.Add(this.button1);
                this.Controls.Add(this.textBox1);
                this.Name = "Form1";
                this.Text = "Form1";
                this.ResumeLayout(false);
                this.PerformLayout();
    
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                SetText(string.Format("{0}", this.counter++));
            }
    
            private void button2_Click(object sender, EventArgs e)
            {
                if (this.thread == null)
                {
                    this.thread = new Thread(new ThreadStart(StressTest));
    
                    thread.Start();
                }
                else
                {
                    this.cancelRequested = true;
                }
            }
        }
    }
    

1 个答案:

答案 0 :(得分:4)

  

你能解释一下在这种情况下发生死锁的原因和方式吗?

当然......由于lock()/ Invoke()组合而发生死锁。

当辅助线程正在运行和更新时,它会获得对象的锁定。然后,辅助线程调用Invoke(),这是一个同步调用。关键是要意识到辅助线程在继续之前实际上等待主UI线程更新TextBox。

单击“设置”按钮时,它会尝试更新,但必须等待辅助线程释放锁定。此时,主UI实际上停止并在lock()行冻结,等待辅助线程释放锁定。在等待释放锁时,主UI线程无法处理任何消息。

但是辅助线程在做什么?它目前有锁,正在等待主UI线程为其同步Invoke()调用提供服务。由于主UI线程正在等待释放锁,但它无法处理任何请求(包括Invoke()请求)和bam ... DEADLOCK!他们都在互相等待。

  

如何避免它?

永远不要在主UI线程中使用lock()。在某些情况下,从Invoke()切换到BeginInvoke()可以解决问题,因为BeginInvoke()是异步的。