今天我不得不修复一些使用线程的旧版VB.NET 1.0代码。问题是从工作线程而不是UI线程更新UI元素。我花了一些时间才发现我可以使用InvokeRequired断言来查找问题。
除了上面提到的并发修改问题外,还有一些可能遇到的死锁,竞争条件等问题。 由于调试/修复线程问题很痛苦,我想知道如何减少这方面的编码错误/错误以及如何更容易找到它们。那么,我要求的是什么,是:
如果适用且可能,请提供一些示例代码。答案应该与.NET框架(任何版本)相关。
答案 0 :(得分:25)
这可能是一个大量的列表 - 阅读Joe Duffy的优秀“Concurrent Programming On Windows”以获取更多细节。这几乎是一次大脑转储...
在调试方面,我没有太多建议。使用Thread.Sleep来提高看到竞争条件和死锁的可能性是可行的,但是在你知道把它放到哪里之前你必须对错误有一个合理的理解。记录非常方便,但不要忘记代码进入某种量子状态 - 通过记录来观察它几乎必然会改变它的行为!
答案 1 :(得分:11)
我不确定这对你正在使用的特定应用程序有多大帮助,但是这里有两种从编写多线程代码的函数编程中借用的方法:
不可变对象
如果需要在线程之间共享状态,则状态应该是不可变的。如果一个线程需要对对象进行更改,它会使用更改创建对象的全新版本,而不是改变对象的状态。
不变性本身并不限制您可以编写的代码类型,也不会效率低下。有许多不可变堆栈的实现,构成映射和集合基础的各种不可变树,以及其他类型的不可变数据结构,并且许多(如果不是全部)不可变数据结构与它们的可变对应物一样有效。
由于对象是不可变的,因此一个线程不可能在您的鼻子下改变共享状态。这意味着您不需要获取锁来编写多线程代码。这种方法消除了与死锁,活锁和竞争条件相关的一整类错误。
Erlang风格的消息传递
您不需要学习该语言,但请查看Erlang以了解它是如何实现并发的。 Erlang应用程序几乎可以无限扩展,因为每个进程都与其他进程完全分离(注意:这些不完全是进程,但也不完全是线程)。
进程启动并简单地旋转循环等待消息:消息以元组的形式接收,然后进程可以模式匹配以查看消息是否有意义。进程可以发送其他消息,但它们对接收消息的人无动于衷。
此样式的优点是消除锁定,当一个进程失败时,它不会导致整个应用程序崩溃。以下是Erlang风格并发的一个很好的总结:http://www.defmacro.org/ramblings/concurrency.html
答案 2 :(得分:2)
使用FIFO。其中很多。这是硬件程序员的古老秘密,它不止一次地保存了我的培根。
答案 3 :(得分:2)
似乎没有人回答如何调试多线程程序的问题。这是一个真正的挑战,因为如果存在错误,则需要实时调查,这对于Visual Studio等大多数工具来说几乎是不可能的。唯一可行的解决方案是编写跟踪,尽管跟踪本身应该:
这听起来像是一项不可能完成的任务,但可以通过将跟踪写入内存来轻松实现。在C#中,它看起来像这样:
public const int MaxMessages = 0x100;
string[] messages = new string[MaxMessages];
int messagesIndex = -1;
public void Trace(string message) {
int thisIndex = Interlocked.Increment(ref messagesIndex);
messages[thisIndex] = message;
}
方法Trace()是多线程安全的,非阻塞的,可以从任何线程调用。在我的电脑上,执行大约需要2微秒,这应该足够快。
在您认为可能出错的地方添加Trace()指令,让程序运行,等到错误发生,停止跟踪,然后调查跟踪是否有任何错误。
此方法的更详细说明,它还收集线程和计时信息,循环缓冲区并输出跟踪,您可以在以下位置找到: CodeProject:实时调试多线程代码1
答案 4 :(得分:0)
这些是编写质量(更易于阅读和理解)多线程代码的步骤: