有没有一种方法可以无限期暂停线程?

34

我在业余时间开发一款基于.NET的网络爬虫应用程序,其中一个功能是添加暂停按钮来暂停特定线程。

我对多线程相对较新,还没有找到一种目前支持无限期暂停线程的方法。我记不清具体的类/方法,但我知道有一种方法可以做到这一点,但已被.NET框架标记为过时。

是否有一种好的通用方法来无限期地暂停C# .NET中的工作线程。

最近我很忙,没有太多时间来开发这个应用程序,上一次我接触它还是在.NET 2.0框架下。我愿意尝试任何新功能(如果有的话)在.NET 3.5框架中存在,但我想知道既适用于2.0框架又可行的解决方案。

7个回答

97

千万不要使用Thread.Suspend。它的主要问题是99%的情况下,当您暂停线程时,您无法知道该线程正在做什么。如果该线程持有锁,则易于陷入死锁等问题。请记住,您调用的代码可能在后台获取/释放锁。Win32有类似的API:SuspendThreadResumeThread。以下关于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();
您可以使用类似的机制来发出退出线程的信号并等待两个事件,检测哪个事件被触发。
为了好玩,我将提供一个完整的示例:

You can use a similar mechanism to signal the thread to exit and wait on both events, detecting which event was signaled.

Just for fun I'll provide a complete example:

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..
        }
    }
}

16

C#中的线程电子书总结Thread.Suspend和Thread.Resume:

废弃的Suspend和Resume方法有两种模式——危险和无用!

该书建议使用同步构造,如AutoResetEventMonitor.Wait来执行线程暂停和恢复。


我得去读那本电子书。我自己从来没有遇到过挂起/恢复的问题。 - Phil Wright

2
如果没有同步要求:

Thread.Sleep(Timeout.Infinite);

(注:该代码可使线程永久休眠,直到外部唤醒)

1
我刚刚实现了一个LoopingThread类,它循环执行传递给构造函数的操作。它基于Brannon的帖子。我还添加了一些其他东西,如WaitForPause()WaitForStop()TimeBetween属性,该属性指示下一次循环之前应等待的时间。
我还决定将while循环更改为do-while循环。这将为连续的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));
  }
}

0
除了上面的建议,我想再加一条提示。在某些情况下,使用BackgroundWorker可以简化您的代码(特别是当您使用匿名方法来定义DoWork和其他事件时)。

0
与其他人所说的一样 - 不要这样做。你真正想做的是“暂停工作”,让你的线程自由漫游。你能给我们一些关于你想暂停的线程的更多细节吗?如果你没有启动线程,你绝对不应该考虑暂停它 - 它不是你的。如果它是你的线程,那么我建议你不要暂停它,而是让它等待更多的工作。Brannon在他的回答中对这个选项提出了一些很好的建议。或者,就让它结束;当你需要时再启动一个新的。

-1

Suspend()和Resume()可能已经过时,但它们绝不是无用的。 例如,如果您有一个线程正在修改数据并进行了漫长的工作,而用户希望停止它,则可以单击按钮。当然,您需要请求验证,但同时您不希望该线程在用户决定真正中止之前继续修改数据。 在等待用户单击确认对话框上的“是”或“否”按钮时暂停线程是防止其在您发出指定的中止事件以允许其停止之前继续修改数据的唯一方法。 对于具有一个循环的简单线程,事件可能很好,但具有复杂处理的复杂线程则是另一回事。 当然,Suspend()绝不能用于同步,因为它的实用性不在于此功能。

这只是我的观点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接