c#lock()在第二次锁定尝试时挂起

时间:2013-10-29 10:47:52

标签: c# multithreading locking

EDIT2 - > 看看底部; 的< - EDIT2

我遇到了(甚至至少对我而言)行为。

我甚至创建了简单的WinForms类和简单的类(下面的代码)来测试它。

我一直认为,如果之前的lock(m_lock)调用没有结束,则调用lock(m_lock),第一个将等待并输入onece,第二个离开锁定范围。不。

行动的流程是:

  1. 创建Class1对象;

  2. 调用Start()方法;

  3. DoSomething()方法锁定m_lock时调用run方法;

  4. 输出是:

      

    开始()

         

    尝试获取锁定

         

    获得锁定

         

    发布锁定

         

    尝试获取锁定

         

    获得锁定

         

    DoSomething()尝试获取锁

         

    ......挂起......

    我错过了什么或做错了什么?我是C#的新手(来自C ++)所以也许C#中有一些 gotchas

    它仍然挂起......(当我写完这篇文章的时候)

    修改 - > 在现实世界中,我使用锁来保护serialPort上的读/写/配置(具有同步读/写,而不是异步读/写)。我在dbg中看到有一些内部WaitOne调用。不知道它是否相关。 的< - 修改

    以下是示例:

    using System;
    
    namespace LockTester
    {
        public class Class1
        {
            object m_lock = null;
            bool m_isRunning;
            System.Threading.Thread m_thread = null;
            public Class1()
            {
                Console.WriteLine("Class1 ctor");
                m_lock = new object();
                m_isRunning = false;
            }
    
            public void DoSomething(){
                Console.WriteLine("DoSomething() Trying to acquire lock");
                lock(m_lock){
                    Console.WriteLine("DoSomething() Acquired lock");
                }
                Console.WriteLine("DoSomething() Released lock");
            }
    
            public void Start(){
                Console.WriteLine("start()");
                m_isRunning = true;
                if (m_thread == null){
                    m_thread = new System.Threading.Thread(Run);
                }
                m_thread.Start();
            }
    
            public void Stop(){
                Console.WriteLine("stop()");
                m_isRunning = false;
            }
    
            private void Run(){
                while (m_isRunning){
                    Console.WriteLine("Trying to acquire lock");
                    lock(m_lock){
                        Console.WriteLine("Acquired lock");
                        System.Threading.Thread.Sleep(1000);
                    }
                    Console.WriteLine("Released lock");
                    System.Threading.Thread.Sleep(1000);
                }
            }
        }
    }
    

    EDIT2:

    好的,找到了答案。这是一个更常见的分母。

    我找到了某个地方(很可能是)一个将控制台输出重定向到TextBox的解决方案(纯粹是测试原因,你知道 - 使用gui的小型测试应用程序,它可以捕获被测试对象的内部消息打印到控制台)。

    以下是代码:

    在我的表单构造函数中使用:

    _writer = new TextBoxStreamWriter(textBox1, this);

    Console.SetOut(_writer);

    public class TextBoxStreamWriter : TextWriter
    {
        TextBox _output = null;
        Form _form = null;
        object _lock = new object();
    
        delegate void SetTextCallback(string text);
    
        private void SetText(string text)
        {
          // InvokeRequired required compares the thread ID of the
          // calling thread to the thread ID of the creating thread.
          // If these threads are different, it returns true.
          if (_output.InvokeRequired)
          { 
            SetTextCallback d = new SetTextCallback(SetText);
            _form.Invoke(d, new object[] { text });
          }
          else
          {
              _output.AppendText(text);
          }
        }
    
    
        public TextBoxStreamWriter(TextBox output, Form form)
        {
            _output = output;
            _form = form;
        }
    
        public override void Write(char value)
        {
            lock (_lock)
            {
                base.Write(value);
                SetText(value.ToString());
            }
        }
    
        public override Encoding Encoding
        {
            get { return System.Text.Encoding.UTF8; }
        }
    }
    

    任何人都可以解释为什么会导致这个问题?

1 个答案:

答案 0 :(得分:2)

当您致电Form.Invoke时,它会执行此操作:

  

在拥有控件底层窗口句柄的线程上执行指定的委托。

这样做的方法是将消息发布到拥有线程的消息队列中,并等待该线程处理该消息。

因此,Invoke阻止调用,在调用被调用的委托之前不会返回。

现在,您的代码阻塞的可能原因是您的主GUI线程已经在等待其他事情发生,可能是您的外部程序已经完成。

因此它实际上并没有处理消息。

如果这是原因,那么这里的解决方案是删除GUI线程的阻塞部分。不要坐在那里等待外部程序完成,而是转出等待它完成的任务,然后在主窗体上引发适当的事件。同时,主线程可以自由处理消息,更新文本框等。

请注意,这意味着如果启动外部程序是为了响应事件(如按钮单击),则可能需要在程序运行时禁用部分用户界面,以避免用户单击按钮两次,开始两次并行执行,都会报告到同一个文本框。

结论:多线程编程很难!