Есть ли способ приостановить поток? - PullRequest
32 голосов
/ 27 сентября 2008

В свободное время я работал над приложением .NET для веб-сканирования, и одной из функций этого приложения, которую я хотел включить, была кнопка паузы для приостановки определенной темы.

Я относительно новичок в многопоточности и не смог найти способ приостановить поток на неопределенный срок, который в настоящее время поддерживается. Я не могу вспомнить точный класс / метод, но я знаю, что есть способ сделать это, но он был помечен как устаревший в .NET Framework.

Есть ли хороший универсальный способ приостановить рабочий поток в C # .NET на неопределенный срок.

В последнее время у меня не было много времени для работы над этим приложением, и последний раз, когда я к нему обращался, был в среде .NET 2.0. Я открыт для любых новых функций (если таковые имеются), которые существуют в платформе .NET 3.5, но я хотел бы знать о решении, которое также работает в среде 2.0, так как это то, что я использую на работе, и было бы хорошо знаю на всякий случай.

Ответы [ 7 ]

92 голосов
/ 27 сентября 2008

Никогда, никогда не используйте 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..
        }
    }
}
16 голосов
/ 27 сентября 2008

В Threading в C # электронная книга суммирует Thread.Suspend и Thread.Resume таким образом:

Устаревшие методы Suspend и Resume имеют два режима - опасный и бесполезный!

В книге рекомендуется использовать конструкцию синхронизации, такую ​​как AutoResetEvent или Monitor.Wait , для выполнения приостановки и возобновления потока.

1 голос
/ 04 июля 2012

Я только что реализовал класс LoopingThread, который зацикливает действие, переданное конструктору. Это основано на посте Браннона. Я добавил некоторые другие вещи, такие как WaitForPause(), WaitForStop() и свойство TimeBetween, которые указывают время, которое следует ожидать перед следующим циклом.

Я также решил изменить цикл while на цикл do-while. Это даст нам детерминированное поведение для последовательных Start() и Pause(). Под детерминистическим я имею в виду, что действие выполняется хотя бы один раз после команды Start(). В реализации Браннона это может быть не так.

Я опустил некоторые вещи для корня вопроса. Такие вещи, как «проверить, был ли поток уже запущен», или шаблон 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 голосов
/ 04 ноября 2016

Если нет требований к синхронизации:

Thread.Sleep(Timeout.Infinite);

0 голосов
/ 12 января 2012

Suspend () и Resume () могут быть ограничены, однако они ни в коем случае не бесполезны. Если, например, у вас есть поток, выполняющий длительную работу по изменению данных, и пользователь хочет остановить его, он нажимает кнопку. Конечно, вам нужно запросить подтверждение, но в то же время вы не хотите, чтобы этот поток продолжал изменять данные, если пользователь решает, что он действительно хочет прервать. Приостановка потока в ожидании, когда пользователь нажмет эту кнопку «Да» или «Нет» в диалоговом окне подтверждения, - это способ only , чтобы предотвратить изменение данных перед тем, как вы сообщите о назначенном событии прерывания, которое позволит ему остановиться. , События могут быть хорошими для простых потоков, имеющих один цикл, но сложные потоки со сложной обработкой - другая проблема. Конечно, Suspend () должен никогда не использоваться для синхронизации, так как его полезность не для этой функции.

Только мое мнение.

0 голосов
/ 14 октября 2008

В соответствии с тем, что говорили другие, не делайте этого. Что вы действительно хотите сделать, так это «приостановить работу» и позволить своим потокам свободно перемещаться. Можете ли вы дать нам более подробную информацию о темах, которые вы хотите приостановить? Если вы не запустили поток, вы определенно не должны даже рассматривать его приостановку - это не ваше. Если это ваша ветка, то я предлагаю вместо того, чтобы приостановить ее, вы просто сидите, ожидая, что еще предстоит сделать. У Брэннона есть несколько превосходных предложений по этому варианту в его ответе. В качестве альтернативы, просто позвольте этому закончиться; и раскрутите новый, когда вам это нужно.

0 голосов
/ 27 сентября 2008

Помимо предложений выше, я хотел бы добавить один совет. В некоторых случаях использование BackgroundWorker может упростить ваш код (особенно если вы используете анонимный метод для определения DoWork и других его событий).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...