web-dev-qa-db-ja.com

スレッドを無期限に一時停止する方法はありますか?

私は空き時間にWebクロールの.NETアプリに取り組んでおり、このアプリに含めたい機能の1つは、特定のスレッドを一時停止する一時停止ボタンでした。

私はマルチスレッドに比較的慣れていないため、現在サポートされているスレッドを無期限に一時停止する方法を見つけることができませんでした。正確なクラス/メソッドを思い出せませんが、これを行う方法があることは知っていますが、.NETフレームワークによって廃止されたフラグが付けられています。

C#.NETでワーカースレッドを無期限に一時停止する汎用的な方法はありますか?.

最近、このアプリで作業する時間があまりなく、最後に触れたのは.NET 2.0フレームワークでした。 .NET 3.5フレームワークに存在する新しい機能(ある場合)を利用できますが、2.0フレームワークでも機能するソリューションについて知りたいのです。念のため知ってください。

32
Dan Herbert

決して使用しないでくださいThread.Suspend。それに関する主な問題は、一時停止したときにそのスレッドが何をしているのかを99%知ることができないということです。そのスレッドがロックを保持していると、デッドロック状態などに陥りやすくなります。呼び出しているコードがバックグラウンドでロックを取得/解放している可能性があることに注意してください。 Win32にも同様のAPIがあります:SuspendThreadおよびResumeThread。次のSuspendThreadのドキュメントは、APIの危険性の概要を示しています。

http://msdn.Microsoft.com/en-us/library/ms686345(VS.85).aspx

この関数は、主にデバッガが使用するように設計されています。スレッドの同期に使用するためのものではありません。 mutexやクリティカルセクションなどの同期オブジェクトを所有するスレッドで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..
        }
    }
}
92
Brannon

C#でのスレッディング 電子ブックは、Thread.SuspendおよびThread.Resumeを次のように要約しています。

非推奨のSuspendメソッドとResumeメソッドには、危険と無用の2つのモードがあります。

この本では、スレッドの中断と再開を実行するために AutoResetEvent または Monitor.Wait などの同期構造を使用することを推奨しています。

16
Shabbyrobe

コンストラクタに渡されたアクションをループするLoopingThreadクラスを実装しました。ブラノンの投稿に基づいています。 WaitForPause()WaitForStop()、およびTimeBetweenプロパティのようなものに他の要素を入れました。これは、次のループの前に待機する時間を示します。

また、whileループをdo-whileループに変更することにしました。これにより、連続するStart()およびPause()の確定的な動作が得られます。決定論とは、アクションがStart()コマンドの後に少なくとも1回実行されることを意味します。ブラノンの実装では、そうではないかもしれません。

問題の根本についていくつか省略しました。「スレッドがすでに開始されているかどうかを確認する」などのこと、または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));
  }
}
1
Matthias

同期要件がない場合:

Thread.Sleep(Timeout.Infinite);

1
Alberto

他の人が言ったことに沿って-それをしないでください。本当にやりたいことは、「作業を一時停止」して、スレッドを自由にローミングさせることです。一時停止するスレッドの詳細を教えてください。スレッドを開始しなかった場合は、スレッドの一時停止も考慮すべきではありません。それはあなたのものではありません。それがあなたのスレッドである場合、私はそれを一時停止するのではなく、あなたがそれを座って、さらなる作業を待つことをお勧めします。ブラノンは彼の反応でこのオプションについていくつかの優れた提案をしています。または、単に終了させます。必要なときに新しいものをスピンアップします。

0
Bruce

上記の提案に加えて、1つのヒントを追加したいと思います。場合によっては、BackgroundWorkerを使用するとコードを簡略化できます(特に、匿名メソッドを使用してDoWorkとそれの他のイベントを定義する場合)。

0
Lex Li