VB.net 多线程问题 - 线程长时间休眠

Posted

技术标签:

【中文标题】VB.net 多线程问题 - 线程长时间休眠【英文标题】:VB.net multithread issue - thread goes to sleep for long times 【发布时间】:2019-06-30 10:14:46 【问题描述】:

我有一个用 VB.net 编写的旧版 winform 应用程序,它有几个线程(3-4 个线程),每个线程在应用程序处于活动状态时循环运行。 每个线程循环都会遍历一个项目列表并检查它们的状态。 例如,一个线程有一个与数据库相关的项目列表,它会检查它们是否发生了变化并采取相应的行动。 另一个线程有一个需要从另一个 TCP 服务器更新的项目列表。 在每个线程完成它的列表后,它就会有 Sleep(300)。 我有一个奇怪的现象,其中一个线程有时会启动,然后我看到其他线程多次运行它们的循环,然后第一个线程才继续。这会导致该线程的循环需要很长时间。

每个线程的循环看起来像这样:

  Do While locParent.ServiceState = IThreadMain.eServiceState.Run 

       ReadItems() ' go through the list
       Threading.Thread.Sleep(300) ' sleep

       Application.DoEvents()
 Loop

在 ReadItems() 代码中也有睡眠调用,时间很短(最多 1000 毫秒)。

我不明白为什么有时一个特定线程会休眠/挂起很长时间,而其他线程循环执行多次。

【问题讨论】:

请永远不要使用Application.DoEvents()。它只是在向后兼容 VB6 的框架中。这对 winforms 应用程序的正常执行流程来说是灾难性的。它将导致几乎无法调试的重入错误。将它们全部删除,然后尝试让您的代码正常工作。 @Enigmativity,我可以用什么代替? 你不需要使用这样的循环。改用计时器 - 这就是你应该使用的。 @Enigmativity 等价物对于在处理代码的过程中获得及时的 UI 更新是必要的。 “从不”太强了,但它应该只用于它实际完成的事情(运行消息循环,直到没有更多消息留下)。 @Craig - 不,这正是它不应该运行的原因。在消息循环中间运行消息循环是导致重入问题的原因。这就是为什么它被称为“重新进入”。 “从不”绝对是正确的词。 【参考方案1】:

线程让 CPU 运行的时间完全由平台 (OS) 管理。您无法预测任意线程何时唤醒。当任意线程获得 CPU 时,由 .NET 框架和操作系统线程调度程序决定。线程是允许以并行方式运行任务的机制,但线程本身在获得 CPU 时间时并不能为您提供任何保证。

如果您需要运行线程的某种顺序,您可以通过某种线程同步机制(例如 class ManualResetEventclass AutoResetEvent)来实现,但在您的情况下,这实际上可能是一种反图案。创建线程是为了让您的代码以并行方式运行,如果没有充分的理由要同步线程(如生产者消费者模式),则不应这样做。

顺便说一句 - 在大多数情况下,让线程在没有任何等待的情况下运行,只是像 Thread.Sleep(100) 这样的人工睡眠在大多数情况下是一种反模式,它会损害系统的整体性能。只有当它们有有意义的工作要做时,才应该运行线程。例如,如果一个线程处理来自 TCP 套接字的一些数据,它应该无限期地休眠,直到来自套接字的一些数据到达。像这样的代码应该以这种方式示意性地编写。

AutoResetEvent Event; // Shared variable.
ConcurrentQueue<SomeDTO> Queue; // Shared thread safe queue.

// THREAD 1.

while(true)

    // Waits indefinitely.
    Event.WaitOne();

     // Read data from thread safe queue.
    SomeDTO var;

    if (Queue.TryDequeue(out var)==true)
    
        // Process data.
    


// THREAD 2 - if new data arrive from TCP.

SomeDTO var=ReadDataFromTCP();

Queue.Enqueue(var);

Event.Set(); // This wakes up THREAD 1.

此算法有一些替代方案,例如如果某些处理直到某个时间才完成并且如果发生这种情况(发送错误消息)需要完成某些处理,则向 Event.WaitOne() 添加一些超时,但通常线程应该'不要'随机等待' 100ms 或 500ms 只是为了防止线程旋转。他们应该等到其他线程唤醒他们,因为他们有一些数据要处理或者一些有意义的超时到期。

如果线程只是为了检查而被唤醒,则没有什么要处理的——这种情况可以通过其他方式来确定(比如我们是否知道来自 TCP 套接字的数据何时到达),这种“随机等待”只是浪费CPU 周期数。

在某些情况下,这种“随机等待”是不可避免的 - (例如,如果 3rd 方 API 未发出异步事件到达的信号),但在大多数情况下,此类代码是代码设计不当的标志。

【讨论】:

谢谢蒂米,感谢您的回答。我希望尽可能少地更改代码,因为它是一个遗留系统:-(我想测试同步机制,但我不确定如何在 vb.net 中实现它,我正在考虑创建一个线程池,并且确保完成循环的线程仅在所有其他“类型”线程也完成循环时再次运行。你有一个很好的例子吗? 如果你真的需要同步线程——这就是问题所在——你可以尝试使用基于任务的编程而不是线程。它比线程更容易实现,因为 VB.NET 和 C# 都具有使您的代码更简单的语言支持。但是,如果它是您无法修改的遗留系统,这可能是一个问题。这是您应该阅读的 MSDN 文档。 docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/…

以上是关于VB.net 多线程问题 - 线程长时间休眠的主要内容,如果未能解决你的问题,请参考以下文章

VB.net 如何设计多线程运行

VB.net多线程编程问题

VB.NET多线程入门

vb.net主线程使用BackgroundWorker进行后台长时间的检索操作的同时怎么做到前台画面的其他操作能够响应?

VB.net可以多线程控制同一个窗体及其控件吗

具有许多数据库调用的 VB.net 多线程