有没有一种方法可以无限期地暂停线程?
我在业余时间一直在开发Web爬网.NET应用程序,而我想包含的该应用程序的功能之一是用于暂停特定线程的暂停按钮。
我对多线程技术还比较陌生,还无法找出当前支持的无限期暂停线程的方法。我不记得确切的类/方法,但是我知道有一种方法可以执行此操作,但是.NET框架已将其标记为已过时。
有没有什么好的通用方法可以无限期地暂停C.NET中的工作线程。
最近我没有太多时间来开发此应用程序,而我上次接触它的时间是在.NET 2.0框架中。我对.NET 3.5框架中存在的任何新功能(如果有的话)持开放态度,但是我想知道在2.0框架中也可用的解决方案,因为这是我在工作中使用的功能,知道以防万一。
解决方案
Cebook中的Threading总结了Thread.Suspend和Thread.Resume:
The deprecated Suspend and Resume methods have two modes – dangerous and useless!
该书建议使用同步结构(例如AutoResetEvent或者Monitor.Wait)执行线程挂起和恢复。
永远不要使用Thread.Suspend
。它的主要问题是,在挂起线程时,有99%的时间我们不知道该线程在做什么。如果该线程持有锁,则我们更容易陷入死锁等情况。请记住,我们正在调用的代码可能正在幕后获取/释放锁。 Win32有一个类似的API:SuspendThread和ResumeThread。以下针对SuspendThread的文档对API的危险性进行了很好的总结:
http://msdn.microsoft.com/zh-CN/library/ms686345(VS.85).aspx
This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. Calling SuspendThread on a thread that owns a synchronization object, such as a mutex or critical section, can lead to a deadlock if the calling thread tries to obtain a synchronization object owned by a suspended thread. To avoid this situation, a thread within an application that is not a debugger should signal the other thread to suspend itself. The target thread must be designed to watch for this signal and respond appropriately.
无限期挂起线程的正确方法是使用ManualResetEvent
。线程很可能在循环,执行一些工作。挂起线程的最简单方法是让线程在每次迭代时"检查"事件,如下所示:
while (true) { _suspendEvent.WaitOne(Timeout.Infinite); // Do some work... }
我们指定一个无限超时,这样当不通知事件时,线程将无限期阻塞,直到通知事件为止,线程将在该点从中断处恢复。
我们可以这样创建事件:
ManualResetEvent _suspendEvent = new ManualResetEvent(true);
" true"参数告诉事件以信号状态开始。
当我们想暂停线程时,请执行以下操作:
_suspendEvent.Reset();
并恢复线程:
_suspendEvent.Set();
我们可以使用类似的机制向线程发出信号以退出并等待两个事件,以检测到哪个事件已被发出信号。
只是为了好玩,我将提供一个完整的示例:
public class Worker { ManualResetEvent _shutdownEvent = new ManualResetEvent(false); ManualResetEvent _pauseEvent = new ManualResetEvent(true); Thread _thread; public Worker() { } public void Start() { _thread = new Thread(DoWork); _thread.Start(); } public void Pause() { _pauseEvent.Reset(); } public void Resume() { _pauseEvent.Set(); } public void Stop() { // Signal the shutdown event _shutdownEvent.Set(); // Make sure to resume any paused threads _pauseEvent.Set(); // Wait for the thread to exit _thread.Join(); } public void DoWork() { while (true) { _pauseEvent.WaitOne(Timeout.Infinite); if (_shutdownEvent.WaitOne(0)) break; // Do the work here.. } } }
除了上面的建议,我想补充一个建议。在某些情况下,使用BackgroundWorker可以简化代码(尤其是当我们使用匿名方法定义DoWork及其它的其他事件时)。
与其他人说的一致,不要这样做。我们真正想要做的是"暂停工作",并让线程自由漫游。我们能为我们提供有关我们要挂起的线程的更多详细信息吗?如果我们没有启动线程,那么我们甚至绝对不应该考虑挂起不是我们自己的线程。如果这是线程,那么我建议我们不要挂起它,而只需坐下来,等待更多工作要做。布兰农在回应中对此选项提出了一些极好的建议。或者,让它结束。并在需要时旋转一个新的。