我在空闲时间一直在使用网络爬行.NET应用程序,我想要包含的这个应用程序的一个功能是暂停按钮来暂停特定的线程。
我对多线程比较陌生,我无法找到一种无限期暂停线程的方法。我不记得确切的类/方法,但我知道有一种方法可以做到这一点,但它被.NET框架标记为过时。
是否有任何好的通用方法可以无限期地暂停C#.NET中的工作线程。
我最近没有花很多时间来处理这个应用程序,最后一次触及它是在.NET 2.0框架中。我对.NET 3.5框架中存在的任何新功能(如果有的话)持开放态度,但是我想知道在2.0框架中也能工作的解决方案,因为这是我在工作中使用的,这对于知道以防万一。
答案 0 :(得分:91)
永远不要使用Thread.Suspend
。它的主要问题是99%的时间你暂停它时你不知道该线程正在做什么。如果该线程持有锁,则可以更容易地进入死锁状态等。请记住,您正在调用的代码可能是在后台获取/释放锁。 Win32有一个类似的API:SuspendThread
和ResumeThread
。以下SuspendThread
文档很好地总结了API的危险性:
http://msdn.microsoft.com/en-us/library/ms686345(VS.85).aspx
此功能主要供调试器使用。它不适用于线程同步。如果调用线程尝试获取挂起线程拥有的同步对象,则在拥有同步对象(如互斥锁或临界区)的线程上调用SuspendThread会导致死锁。为了避免这种情况,应用程序中不是调试器的线程应该通知另一个线程暂停自身。目标线程必须设计为监视此信号并做出适当的响应。
无限期暂停线程的正确方法是使用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..
}
}
}
答案 1 :(得分:15)
Threading in C#电子书总结了Thread.Suspend和Thread.Resume:
不推荐使用的Suspend和Resume方法有两种模式 - 危险和无用!
本书建议使用同步构造(如AutoResetEvent或Monitor.Wait)来执行线程暂停和恢复。
答案 2 :(得分:1)
我刚刚实现了一个LoopingThread
类,它循环传递给构造函数的动作。它基于布兰农的帖子。我在其中添加了一些其他内容,例如WaitForPause()
,WaitForStop()
和TimeBetween
属性,表示在下一次循环之前应该等待的时间。
我还决定将while循环更改为do-while-loop。这将为我们提供连续Start()
和Pause()
的确定性行为。确定性我的意思是,在Start()
命令之后该动作至少执行一次。在Brannon的实现中,情况可能并非如此。
我省略了一些事情的根源。诸如“检查线程是否已经启动”或IDisposable
模式。
public class LoopingThread
{
private readonly Action _loopedAction;
private readonly AutoResetEvent _pauseEvent;
private readonly AutoResetEvent _resumeEvent;
private readonly AutoResetEvent _stopEvent;
private readonly AutoResetEvent _waitEvent;
private readonly Thread _thread;
public LoopingThread (Action loopedAction)
{
_loopedAction = loopedAction;
_thread = new Thread (Loop);
_pauseEvent = new AutoResetEvent (false);
_resumeEvent = new AutoResetEvent (false);
_stopEvent = new AutoResetEvent (false);
_waitEvent = new AutoResetEvent (false);
}
public void Start ()
{
_thread.Start();
}
public void Pause (int timeout = 0)
{
_pauseEvent.Set();
_waitEvent.WaitOne (timeout);
}
public void Resume ()
{
_resumeEvent.Set ();
}
public void Stop (int timeout = 0)
{
_stopEvent.Set();
_resumeEvent.Set();
_thread.Join (timeout);
}
public void WaitForPause ()
{
Pause (Timeout.Infinite);
}
public void WaitForStop ()
{
Stop (Timeout.Infinite);
}
public int PauseBetween { get; set; }
private void Loop ()
{
do
{
_loopedAction ();
if (_pauseEvent.WaitOne (PauseBetween))
{
_waitEvent.Set ();
_resumeEvent.WaitOne (Timeout.Infinite);
}
} while (!_stopEvent.WaitOne (0));
}
}
答案 3 :(得分:0)
除了上面的建议,我还想添加一个提示。在某些情况下,使用BackgroundWorker可以简化代码(特别是当您使用匿名方法定义DoWork及其他事件时)。
答案 4 :(得分:0)
与其他人所说的一致 - 不要这样做。你真正想做的是“暂停工作”,让你的线程自由漫游。你能给我们一些关于你想暂停的线程的更多细节吗?如果你没有启动该线程,你绝对不应该考虑暂停它 - 它不是你的。如果它是你的主题,那么我建议不要暂停它,你只需要坐下来,等待更多工作要做。 Brannon在回答中对此选项提出了一些很好的建议。或者,让它结束;并在需要时启动一个新的。
答案 5 :(得分:0)
如果没有同步要求:
Thread.Sleep(Timeout.Infinite);
答案 6 :(得分:-1)
Suspend()和Resume()可能会被删除,但它们绝不是无用的。 例如,如果你有一个线程做了很长的工作来改变数据,并且用户希望停止它,他点击一个按钮。当然,您需要请求验证,但同时您不希望该线程继续更改数据,如果用户决定他确实想要中止。 挂起线程,同时等待用户单击确认对话框中的“是”或“否”按钮是唯一方式,以防止它改变数据,然后发出指定的中止事件信号,允许它停。 对于具有一个循环的简单线程,事件可能是好的,但是具有复杂处理的复杂线程是另一个问题。 当然,Suspend()必须从不用于同步,因为它的用处不适用于此功能。
只是我的意见。