在多线程应用程序中冻结

本文关键字:冻结 应用程序 多线程 | 更新日期: 2023-09-27 18:01:08

我正在尝试制作一个WinForms多线程应用程序,它在两个不同的线程中无休止地生成异常。

一个线程使用GenerateDllNotFoundExc()方法,另一个使用另一个方法,这基本上是相同的,但只是生成另一个异常。

然后,它将异常消息写入队列,然后从队列写入文本框。

然而,GUI总是在1秒后冻结,它会在文本框中写入一些消息并冻结。我试着调试它,代码本身可以工作,但是GUI是冻结的。

有人能告诉我我做错了什么吗?

private delegate void GetQueueElem();
private event GetQueueElem getqueuelem;     
private void GenerateDllNotFoundExc()
{
    Action<String> addelem = new Action<String>(AddToQueue);
    string exdll = string.Empty;
    while (shouldgeneratemore)
    {
        try 
        {
            throw new DllNotFoundException();
        }
        catch (Exception ex) 
        {
            exdll = ex.Message;
        }
        this.Invoke(addelem, exdll);
    }
}
private void AddToQueue(string exmess)
{            
    lock (lockobject)            
        queue.Enqueue(exmess);
    getqueuelem.Invoke();
}        
private void AddToTextBox()
{           
     while (queue.Count > 0)
     {
         string s = queue.Dequeue() +"'t" + Thread.CurrentThread.Name 
             + "'t" + Thread.CurrentThread.ManagedThreadId + "'t";
         lock (lockobject)
             textBox1.Text += s;                
     }                     
}       

在多线程应用程序中冻结

这个问题很有教育意义,它显示了三个主要线程错误都存在的证据。按照它们的常见程度大致排列:

  1. 线程竞赛错误。当一个线程读取一个被另一个线程修改的变量时触发。需要锁定以避免造成问题。此代码使用lock关键字,但没有正确使用它。Queue类不是线程安全的,在这段代码中,不安全的Count属性和Dequeue((方法都是在没有锁的情况下使用的。然而,这并不是真正的问题,没有一个使用Queue的代码实际在多个线程上运行。换句话说,实际上并不需要

  2. 僵局。当代码以不可预测的顺序获取锁时发生。对于在程序的UI线程上运行的代码来说,它通常会获取不可见的锁,这些锁内置于.NET Framework、操作系统或各种第三方挂钩中。例如,屏幕阅读器。Invoke((方法特别容易出现死锁,应该极力避免,BeginInvoke(((始终是首选方法。您实际上不需要Invoke((,也不关心返回值。然而,这并不是这个程序中的实际错误,即使它看起来像很多一样的死锁,你也可以使用调试器,看到UI线程正在执行代码,并且没有在锁上停止。

  3. 消防水管的窃听器。当产生结果的线程比处理结果的线程消耗的速度快时,就会发生火疗。这种错误会产生各种各样的痛苦,看起来很像僵局。最终,当内存耗尽时,这样的程序总是会崩溃,被包含太多尚未处理的结果的队列占用。顺便说一句,.NET程序有很多可用内存,这需要一段时间。

它在这个节目中排名第三。UI线程需要执行多个职责,并以高优先级处理调用请求。正在调度调用的方法,在本例中为AddToQueue((。它从内部队列读取调用请求,并尝试在执行其他优先级较低的任务之前先清空队列。当队列无法清空时,就会出现错误,因为工作线程向队列添加条目的速率高于UI线程清空队列的速率。换句话说,UI线程永远跟不上,它只调度调用请求,而不做任何其他事情。

例如,在任务管理器中可以很明显地看到,你会看到你的程序燃烧了100%的核心。所以你知道它实际上并不是死锁。在你的UI中非常明显,你可以点击"停止"按钮,但它没有任何效果。绘画不再发生,被视为一项低优先级任务,只有在不需要发生更重要的事情时才会执行。它看起来完全冻结了,即使UI线程像黑帮杀手一样运行。

消防水管漏洞很容易出错,每秒只需要一千多个调用请求。这取决于UI线程需要做多少工作。通常情况下,更新UI非常昂贵。设置TextBox的Text属性没有什么微妙之处,很多工作都是在幕后进行的。那个天真无邪的+=运算符燃烧了很多循环。除了SendMessage((与本机TextBox对话的静态开销之外,还需要不断地重新分配内部文本缓冲区,从而消耗大量周期。比较字符串与StringBuilder。或者换句话说,即使你一开始没有遇到消防软管错误,你也可以保证迟早会遇到,因为TextBox包含了太多需要从一个缓冲区移动到另一个缓冲区时的文本。在你的情况下,越早越好。

最终,像这样的消防水管漏洞是一个平衡漏洞。你更新UI的速度远远高于人类所能观察到的速度。这不是一个有用的用户界面。这个程序没有实用的建议,它太合成了,故意放慢工作线程的速度是一个解决办法。