繁体   English   中英

从UI调用System.Threading.Thread时锁定挂起

[英]Lock hangs when called from UI to System.Threading.Thread

编辑:请查看问题历史记录,以保持未解决的问题,以免使评论无效。

我点击执行某些代码的按钮,它创建一个线程(System.Threading.Thread)。 当我重新点击开始进程的按钮时,它会挂起并冻结ui。 可能是什么原因?

public partial class ucLoader : UserControl
{
    //lock object for whole instance of class ucLoader
    private object lockUcLoader = new object();

    //bringing info from ui
    private void btnBringInfo_Click(object sender, EventArgs e)
    {
        lock (lockUcLoader)
        {
            btnBringInfo_PerformClick(false);
        }
    }

    //using this method because it could be called when even button not visible
    internal void btnBringInfo_PerformClick(bool calledFromBandInit)
    {
        lock (lockUcLoader) //HANGS HERE when called multiple times and ui freeze as well
        //by the way I am using (repetitive) lock, because this method also called independently from btnBringInfo_Click
        {
            //...
            this.btnLoad_PerformClick();
        }
    }

    //Another button perform click that could be triggered elsewhere when even button not visible
    private void btnLoad_PerformClick()
    {
        lock (lockUcLoader) //I am using (repetitive) lock, because this method also called independently from btnBringInfo_PerformClick
        {
            //...
            Run();  
        }
    }

    //method for creating thread which System.Threading.Thread  
    private void Run()
    {
       lock (lockUcLoader)  //Maybe this lock is NOT REQUIRED, as it is called by only btnLoad_PerformClick(), could you please confirm?
        {
            //some code that thread can be killed when available,  you can ingore this two lines as they are irrelevant to subject, I think
            Source = new CancellationTokenSource();
            Token = Source.Token;
            var shell = new WindowsShell();
            Thread = new Thread((object o) =>
            {
                //...
                var tokenInThread = (CancellationToken)o;
                exitCode =TaskExtractBatchFiles(cls, shell, exitCode);


                 using (var logEnt = new logEntities())
                {

                        //Do some db operation
                        //...
                        this.Invoke((MethodInvoker)delegate
                        {
                            //do some ui update operation
                            //...
                        });
                    }
            }
            Thread.Start(Token);
        }      
    } 

    public void Progress(string message)
    {
        Invoke((MethodInvoker)delegate  //ATTENTION HERE see below picture Wait occurs here
        {
            if (message != null && message.Trim() != string.Empty)
            {
                this.txtStatus.AppendText(message + Environment.NewLine);
            }
        });
    }       
}       

为了避免得到封闭的问题,我的问题是如何防止以下方法可以从后台线程和ui线程中锁定出来

public void Progress(string message)
        {
            Invoke((MethodInvoker)delegate  //ATTENTION HERE see below picture Wait occurs here
            {
                if (message != null && message.Trim() != string.Empty)
                {
                    this.txtStatus.AppendText(message + Environment.NewLine);
                }
            });
        }   

在此输入图像描述

在此输入图像描述

   Invoke((MethodInvoker)delegate ...

每当你在代码中使用lock语句时,你总是冒着引发死锁的风险。 其中一个经典的线程错误。 您通常需要至少两个锁才能到达那里,以错误的顺序获取它们。 是的,你的计划中有两个。 一个你宣称自己。 而且你看不到它,因为它被埋在管道内,使得Control.Invoke()工作。 无法看到锁定是导致死锁成为调试难题的原因。

你可以推断出来,Control.Invoke中的锁是必要的,以确保工作线程被阻塞,直到UI线程执行委托目标。 可能还有助于推断出该程序陷入僵局的原因。 您启动了工作线程,它获取了lockUcLoader锁并开始执行其工作,同时调用Control.Invoke。 现在,在工作完成之前单击按钮,它必然会阻止。 但这使得UI线程变得紧张,并且不再能够执行Control.Invoke代码。 因此工作线程挂起在Invoke调用上,它不会释放锁。 并且UI线程永远挂在锁上,因为工作者无法完成,死锁城市。

Control.Invoke来自.NET 1.0,这是一个框架版本,在与线程相关的代码中有几个严重的设计错误。 虽然本来是有帮助的,但他们只是为程序员设置了陷入困境的死亡陷阱。 Control.Invoke的独特之处在于它永远不会正确使用它。

区分Control.Invoke和Control.BeginInvoke。 只有在需要返回值时才需要调用。 注意你不这样做,使用BeginInvoke代替足够好并立即解决死锁问题。 您可以考虑使用Invoke从UI获取值,以便在工作线程中使用它。 但这导致了其他主要的线程问题,一个线程竞争错误,工作者不知道UI处于什么状态。比如说,用户可能正在忙着与它进行交互,键入一个新值。 你无法知道你获得了什么价值,它很容易成为过时的旧价值。 不可避免地在UI和正在完成的工作之间产生不匹配。 避免这种不幸事故的唯一方法是阻止用户键入新值,使用Enable = false轻松完成。 但是现在使用Invoke不再有意义,你可以在启动线程时传递值。

因此,使用BeginInvoke已足以解决问题。 但那不是你应该停下来的地方。 在Click事件处理程序中没有任何关键点,他们所做的就是让UI无响应,这极大地困扰了用户。 您必须执行的操作是将这些按钮的启用属性设置为false 完成工作后将它们设置为true 现在它不会再出错了,你不需要锁,用户可以得到很好的反馈。

还有另一个严重的问题,你还没有遇到,但你必须解决。 UserControl无法控制其生命周期,当用户关闭托管它的表单时,它会被释放。 但是这与工作线程执行完全不同步,即使控件作为一个doornail失效,它也会一直调用BeginInvoke。 这将使你的程序炸弹,希望在ObjectDisposedException上。 锁无法解决的线程竞争错误。 表单必须有所帮助,它必须主动阻止用户关闭它。 关于这个Q + A中的这个错误的一些注释。

为了完整性,我应该提到第三个最常见的线程错误,这样的代码可能会受到影响。 它没有官方名称,我称之为“firehose bug”。 它发生在工作线程经常调用BeginInvoke时,给UI线程做太多工作。 容易发生,每秒调用它超过一千次就足够了。 UI线程开始烧录100%核心,试图跟上调用请求,永远无法赶上。 容易看到,它停止绘画本身并响应输入,以较低优先级执行的职责。 这需要以逻辑方式修复,每秒更新UI超过25次只会产生人眼无法观察到的模糊,因此毫无意义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM