在多线程应用程序中冻结
Freezes in multithreading app
我正在尝试制作一个 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;
}
}
这个问题很有教育意义,它显示了存在所有三个主要线程错误的证据。将它们按常见程度大致排序:
线程竞争错误。当一个线程读取一个被另一个线程修改的变量时触发。需要锁定以避免导致问题。此代码使用了 lock 关键字,但没有正确使用它。 Queue class 不是线程安全的,在此代码中,不安全的 Count 属性 和 Dequeue() 方法都在没有锁定的情况下使用。然而,这不是这里的实际问题,none 使用 Queue 的代码实际上在多个线程上运行。换句话说,实际上并不需要锁。
死锁。当代码以不可预知的顺序获取锁时发生。对于在程序的 UI 线程上运行的代码来说尤其令人讨厌,它通常会获取不可见的锁,这些锁内置于 .NET Framework、操作系统或各种第 3 方挂钩中。例如屏幕阅读器。 Invoke() 方法特别容易出现死锁,应强烈避免,BeginInvoke() 始终是首选。您实际上 不需要 Invoke(),您不关心 return 值。然而,这不是该程序中的实际错误,即使它看起来像死锁一样 lot,您可以使用调试器并看到 UI 线程正在执行代码并且没有停止一把锁。
消防水龙带故障。当产生结果的线程比处理结果的线程可以消耗的速度更快时,就会发生 Fire-hosing。这种错误会产生各种痛苦,它看起来很像死锁。最终,这样的程序总是会在内存不足时崩溃,内存被包含太多尚未处理的结果的队列消耗。顺便说一句,.NET 程序有很多可用内存。
在这个节目中是第3个。 UI 线程需要执行多个任务,并以高优先级处理调用请求。调度调用的方法,在本例中为 AddToQueue()。它从内部队列中读取调用请求,并尝试先清空队列,然后再执行其他优先级较低的任务。当无法清空队列时,这会出错,因为工作线程以高于 UI 线程可以清空它的速率向队列添加条目。换句话说,UI 线程永远跟不上,它只分派调用请求而不会抽空做任何其他事情。
例如,在任务管理器中非常明显,您会看到您的程序 100% 消耗了核心资源。所以你知道这实际上不是死锁。在您的 UI 中非常明显,您可以点击“停止”按钮,但它没有任何效果。绘画不再发生,被视为低优先级任务,只有在没有更重要的事情需要发生时才会执行。它 看起来 完全冻结,即使 UI 线程 运行 像个大人物。
消防水管错误很容易被绊倒,每秒只需要一千多个调用请求。取决于 UI 线程需要做多少工作。通常很多,更新 UI 通常非常昂贵。设置 TextBox 的 Text 属性 没什么特别的,很多工作都是在幕后进行的。那个看起来无辜的 += 运算符会消耗 lot 个周期。除了 SendMessage() 与本机 TextBox 对话的静态开销之外,还需要不断地重新分配内部文本缓冲区来消耗大量周期。比较 String 与 StringBuilder。或者换句话说,即使您一开始没有遇到消防水龙头错误,也可以保证您迟早会遇到这种情况,因为 TextBox 包含太多需要从一个缓冲区移动到另一个缓冲区的文本。你的情况会更快。
最终,像这样的消防水管错误是一个平衡错误。您正在以 UI 的速度更新 far ,远高于人类所能观察到的速度。那不是一个有用的用户界面。这个程序没有实用的建议,它太综合了,故意减慢工作线程是一种解决方法。
我正在尝试制作一个 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;
}
}
这个问题很有教育意义,它显示了存在所有三个主要线程错误的证据。将它们按常见程度大致排序:
线程竞争错误。当一个线程读取一个被另一个线程修改的变量时触发。需要锁定以避免导致问题。此代码使用了 lock 关键字,但没有正确使用它。 Queue class 不是线程安全的,在此代码中,不安全的 Count 属性 和 Dequeue() 方法都在没有锁定的情况下使用。然而,这不是这里的实际问题,none 使用 Queue 的代码实际上在多个线程上运行。换句话说,实际上并不需要锁。
死锁。当代码以不可预知的顺序获取锁时发生。对于在程序的 UI 线程上运行的代码来说尤其令人讨厌,它通常会获取不可见的锁,这些锁内置于 .NET Framework、操作系统或各种第 3 方挂钩中。例如屏幕阅读器。 Invoke() 方法特别容易出现死锁,应强烈避免,BeginInvoke() 始终是首选。您实际上 不需要 Invoke(),您不关心 return 值。然而,这不是该程序中的实际错误,即使它看起来像死锁一样 lot,您可以使用调试器并看到 UI 线程正在执行代码并且没有停止一把锁。
消防水龙带故障。当产生结果的线程比处理结果的线程可以消耗的速度更快时,就会发生 Fire-hosing。这种错误会产生各种痛苦,它看起来很像死锁。最终,这样的程序总是会在内存不足时崩溃,内存被包含太多尚未处理的结果的队列消耗。顺便说一句,.NET 程序有很多可用内存。
在这个节目中是第3个。 UI 线程需要执行多个任务,并以高优先级处理调用请求。调度调用的方法,在本例中为 AddToQueue()。它从内部队列中读取调用请求,并尝试先清空队列,然后再执行其他优先级较低的任务。当无法清空队列时,这会出错,因为工作线程以高于 UI 线程可以清空它的速率向队列添加条目。换句话说,UI 线程永远跟不上,它只分派调用请求而不会抽空做任何其他事情。
例如,在任务管理器中非常明显,您会看到您的程序 100% 消耗了核心资源。所以你知道这实际上不是死锁。在您的 UI 中非常明显,您可以点击“停止”按钮,但它没有任何效果。绘画不再发生,被视为低优先级任务,只有在没有更重要的事情需要发生时才会执行。它 看起来 完全冻结,即使 UI 线程 运行 像个大人物。
消防水管错误很容易被绊倒,每秒只需要一千多个调用请求。取决于 UI 线程需要做多少工作。通常很多,更新 UI 通常非常昂贵。设置 TextBox 的 Text 属性 没什么特别的,很多工作都是在幕后进行的。那个看起来无辜的 += 运算符会消耗 lot 个周期。除了 SendMessage() 与本机 TextBox 对话的静态开销之外,还需要不断地重新分配内部文本缓冲区来消耗大量周期。比较 String 与 StringBuilder。或者换句话说,即使您一开始没有遇到消防水龙头错误,也可以保证您迟早会遇到这种情况,因为 TextBox 包含太多需要从一个缓冲区移动到另一个缓冲区的文本。你的情况会更快。
最终,像这样的消防水管错误是一个平衡错误。您正在以 UI 的速度更新 far ,远高于人类所能观察到的速度。那不是一个有用的用户界面。这个程序没有实用的建议,它太综合了,故意减慢工作线程是一种解决方法。