C# 在多线程应用程序中冻结

C# 在多线程应用程序中冻结,c#,multithreading,winforms,thread-safety,C#,Multithreading,Winforms,Thread Safety,我正在尝试制作一个WinForms多线程应用程序,它在两个不同的线程中无休止地生成异常 一个线程使用GenerateDllNotFoundExc()方法,另一个线程使用另一个方法,这基本相同,但只是生成另一个异常 然后,它将异常消息写入队列,然后从队列写入文本框 然而,GUI总是在1秒后冻结,它会将消息写入文本框,然后冻结。我试着调试它,代码本身可以工作,但是GUI被冻结了 有人能告诉我我做错了什么吗 private delegate void GetQueueElem(); private e

我正在尝试制作一个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;                
     }                     
}       
private委托void getqueuelem();
私有事件getqueuelem getqueuelem;
私有void GenerateDllNotFoundExc()
{
Action addelem=新操作(AddToQueue);
string exdll=string.Empty;
while(shouldgeneratemore)
{
尝试
{
抛出新的DllNotFoundException();
}
捕获(例外情况除外)
{
exdll=ex.消息;
}
调用(addelem,exdll);
}
}
私有void AddToQueue(字符串exmess)
{            
锁定(锁定对象)
queue.Enqueue(exmess);
getqueuelem.Invoke();
}        
私有void AddToTextBox()
{           
而(queue.Count>0)
{
字符串s=queue.Dequeue()+“\t”+Thread.CurrentThread.Name
+“\t”+Thread.CurrentThread.ManagedThreadId+”\t”;
锁定(锁定对象)
textBox1.Text+=s;
}                     
}       

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

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

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

  • 消防水龙带虫。当产生结果的线程的速度比处理结果的线程的速度快时,就会发生FireHosing。这种错误会产生各种各样的痛苦,看起来很像死锁。最终,这样一个程序总是在内存耗尽时崩溃,被一个包含太多尚未处理的结果的队列所消耗。顺便说一句,.NET程序有很多可用内存

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

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

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


    最终,像这样的消防水管缺陷是一个平衡缺陷。您正在以一种远远高于人类所能观察到的速度更新UI。这不是一个有用的用户界面。这个程序没有实用的建议,它太过合成,故意降低工作线程的速度将是一个解决办法。

    您在哪里为“shouldgeneratemore”赋值的?为什么在
    循环中抛出异常?这有什么意义?在开始时,使用private delegate void getqueuelem()@YuvalItzchakov我希望应用程序无休止地抛出异常,并且当按下停止按钮时,我将“shouldgeneratemore”值设置为“false”以停止