1

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

  1. 运行应用程序(在调试模式下)。
  2. 单击切换按钮让文本在文本框中运行。
  3. 单击设置按钮。-> 这一步发生死锁。

你能解释一下在这种情况下为什么会发生死锁以及如何发生死锁吗?如何避免?

下面是代码:

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;
            }
        }
    }
}
4

1 回答 1

4

你能解释一下在这种情况下为什么会发生死锁以及如何发生死锁吗?

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

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

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

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

如何避免?

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

于 2013-10-27T05:23:36.880 回答