2012-05-06 169 views
12

如何暂停/恢复线程?一旦我Join()一个线程,我无法重新启动它。 那么,如何启动一个线程,并在按下“暂停”按钮时暂停,并在按下恢复按钮时恢复它?如何暂停/恢复线程

此线程所做的唯一事情是在标签控件中显示一些随机文本。

+1

你可以在你的问题中加入你的代码 – Adil

+0

目前没有代码。我仍然在思考如何实施这个我脑海中的项目。我可以告诉你,我从使用线程的工作中获得的经验知道,一旦线程与主线程连接在一起,就无法恢复/重启线程。 – Yustme

回答

16

也许ManualResetEvent是一个不错的选择。 一个简短的例子:

private static EventWaitHandle waitHandle = new ManualResetEvent(initialState: true); 

// Main thread 
public void OnPauseClick(...) { 
    waitHandle.Reset(); 
} 

public void OnResumeClick(...) { 
    waitHandle.Set(); 
} 

// Worker thread 
public void DoSth() { 
    while (true) { 
    // show some random text in a label control (btw. you have to 
    // dispatch the action onto the main thread) 
    waitHandle.WaitOne(); // waits for the signal to be set 
    } 
} 
+0

在VirtualBox XP中不起作用。不知道为什么? – zionpi

+0

我很想使用[_reserved keyword_](https://msdn.microsoft.com/en-us/library/x53a06bb.aspx)作为变量名称,但该解决方案对我来说完全适用于巨魔:-) – MDMoore313

+0

我没有定义事件(♡Rx♡),所以我不知道'event'是一个关键字:-) –

5

我可以建议你阅读Threading in C#, by Joe Albahari,特别Suspend and Resume部分:

线程可以明确地暂停,并通过过时方法Thread.Suspend和Thread.resume恢复。这种机制与阻塞机制完全分开。两个系统都是独立的,并行运行。

一个线程可以挂起自己或另一个线程。调用Suspend会导致线程暂时进入SuspendRequested状态,然后在达到垃圾收集的安全点时进入Suspended状态。从那里,它只能通过另一个调用其Resume方法的线程来恢复。恢复将只在暂停的线程上工作,而不是在阻塞的线程上工作。

从.NET 2.0开始,暂停和恢复已被弃用,由于任意挂起另一个线程的内在危险而不鼓励使用它们。如果暂挂关键资源上的锁定线程,则整个应用程序(或计算机)可能会死锁。这比调用Abort更危险 - 这会导致任何这样的锁都会通过finally块中的代码被释放(至少在理论上)。

SuspendRequested state

+0

你的区块引用区分了挂起的线程和被阻塞的线程,但该图似乎没有做出区分。这是故意的吗? – Tung

+0

@gliderkite,请参阅我对Adil有关显示代码的回复。 – Yustme

+0

@Tung由于某种原因,例如当正在沉睡或等待另一个线程通过Join或EndInvoke结束时,线程被视为被阻止。在代码中:'bool blocked =(someThread.ThreadState&ThreadState.WaitSleepJoin)!= 0;'。那么为什么图中没有区别。见[this](http://www.albahari.com/threading/part2.aspx#_Blocking)。 – gliderkite

2

手动暂停和恢复线程并不是最好的主意。但是,您可以使用线程同步基元很容易地模拟此行为(如ManualResetEvent

看一看this question,可能会发现它有帮助。

但我相信你可以很容易地通过使用计时器来实现“在标签控件中显示随机文本”的目标。

下面是使用DispatcherTimer

var timer = new DispatcherTimer(); 
timer.Tick += (s, e) => Label.Text = GetRandomText(); 
timer.Interval = TimeSpan.FromMilliseconds(500); 
timer.Start(); 

一个简单的例子,您可以通过调用timer.Stop()再次停顿,然后timer.Start()恢复。

+0

嗨,这看起来不错,我可以把这个代码放在一个单独的线程?因为我不想阻塞GUI线程。 – Yustme

+0

@Yustme:AFAIK'DispatcherTimer'的代码在GUI线程上执行。如果你想单独使用'System.Threading.Timer'。 – Tudor

+0

都铎王朝是正确的,所以如果你有一些'沉重'的代码需要在定时器的时钟上执行,使用单独的线程。但是如果你只需要更新文本块,使用DispatcherTimer就可以,并且不会阻塞你的UI。 –

1

这里有两种方法适用于我。两者都假定工作线程有它自己的处理循环。

  1. 纷纷跟帖调用回调请求允许继续下去
  2. 有父调用的线程的类中的方法,以表示它

下面的控制台应用程序示例说明了这两种方法,使用暂停/继续的回调以及停止的工人方法。回调方法的另一个优点是在检查是否允许继续时传回状态更新也很方便。

using System; 
using System.Threading; 

namespace ConsoleApplication7 
{ 
    class Program 
    { 
     static bool keepGoing; 
     static void Main(string[] args) 
     { 
      keepGoing = true; 
      Worker worker = new Worker(new KeepGoingDelegate(KeepGoing)); 
      Thread thread = new Thread(worker.DoWork); 
      thread.IsBackground = true; 
      thread.Start(); 

      while (thread.ThreadState != ThreadState.Stopped) 
      { 
       switch (Console.ReadKey(true).KeyChar) 
       { 
        case 'p': 
         keepGoing = false; 
         break; 
        case 'w': 
         keepGoing = true; 
         break; 
        case 's': 
         worker.Stop(); 
         break; 
       } 
       Thread.Sleep(100); 
      } 
      Console.WriteLine("Done"); 
      Console.ReadKey(); 
     } 

     static bool KeepGoing() 
     { 
      return keepGoing; 
     } 
    } 

    public delegate bool KeepGoingDelegate(); 
    public class Worker 
    { 
     bool stop = false; 
     KeepGoingDelegate KeepGoingCallback; 
     public Worker(KeepGoingDelegate callbackArg) 
     { 
      KeepGoingCallback = callbackArg; 
     } 

     public void DoWork() 
     { 
      while (!stop) 
      { 
       Console.Write(KeepGoingCallback()?"\rWorking":"\rPaused "); 

       Thread.Sleep(100); 
      } 
      Console.WriteLine("\nStopped"); 
     } 

     public void Stop() 
     { 
      stop = true; 
     } 
    } 
} 
+0

嗨,对于我的生活,我无法理解代表。我可以想出如何停止线程,但我不明白你是如何重新启动它? – Zev

+0

@Zev - 委托是一个方法的占位符,所以你可以将它传递给另一个类的实例,以便它可以执行它。在我的示例中,父节点将'KeepGoing()'方法传递给worker,它会周期性地调用以检查父节点是否应该继续。工作线程不会每次都重新启动 - 它会启动一次并持续运行,直到它收到停止信号。 –