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





Электронная книга Многопоточность в C# резюмирует Thread.Suspend и Thread.Resume следующим образом:
The deprecated Suspend and Resume methods have two modes – dangerous and useless!
В книге рекомендуется использовать конструкцию синхронизации, такую как AutoResetEvent или Monitor.Wait, для выполнения приостановки и возобновления потока.
Никогда, никогда не используйте Thread.Suspend. Основная проблема заключается в том, что в 99% случаев вы не можете знать, что делает этот поток, когда вы его приостанавливаете. Если этот поток удерживает блокировку, вам будет проще попасть в тупиковую ситуацию и т. д. Имейте в виду, что код, который вы вызываете, может получать / снимать блокировки за кулисами. Win32 имеет аналогичный API: SuspendThread и ResumeThread. Следующие документы для SuspendThread дают хорошее резюме опасностей API:
http://msdn.microsoft.com/en-us/library/ms686345(VS.85).aspx
This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. Calling SuspendThread on a thread that owns a synchronization object, such as a mutex or critical section, can lead to a deadlock if the calling thread tries to obtain a synchronization object owned by a suspended thread. To avoid this situation, a thread within an application that is not a debugger should signal the other thread to suspend itself. The target thread must be designed to watch for this signal and respond appropriately.
Правильный способ приостановить поток на неопределенный срок - использовать 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..
}
}
}
Помимо предложений выше, я хотел бы добавить один совет. В некоторых случаях использование BackgroundWorker может упростить ваш код (особенно когда вы используете анонимный метод для определения DoWork и других его событий).
В соответствии с тем, что сказали другие - не делайте этого. Что вы действительно хотите сделать, так это «приостановить работу» и позволить вашим потокам свободно перемещаться. Не могли бы вы дать нам более подробную информацию о цепочках, которые вы хотите приостановить? Если вы не запускали ветку, вам определенно не стоит даже думать о ее приостановке - она не ваша. Если это ваш поток, то я предлагаю вместо того, чтобы приостанавливать его, просто оставьте его в покое, ожидая, пока будет выполнена дополнительная работа. В своем ответе Браннон предлагает несколько отличных предложений по этому поводу. Или просто позвольте этому закончиться; и запускайте новый, когда вам это нужно.
Suspend () и Resume () могут быть лишены смысла, однако они никоим образом не бесполезны. Если, например, у вас есть поток, выполняющий длительную работу по изменению данных, и пользователь желает остановить его, он нажимает кнопку. Конечно, вам нужно запросить подтверждение, но в то же время вы не хотите, чтобы этот поток продолжал изменять данные, если пользователь решит, что он действительно хочет прервать выполнение. Приостановка потока в ожидании нажатия пользователем кнопки «Да» или «Нет» в диалоговом окне подтверждения - это способ Только предотвратить изменение данных, прежде чем вы сигнализируете о назначенном событии прерывания, которое позволит ему остановиться. События могут быть удобны для простых потоков, имеющих один цикл, но еще одна проблема - сложные потоки со сложной обработкой. Конечно, Suspend () должна использоваться для синхронизации никогда, поскольку ее полезность не для этой функции.
Только мое мнение.
Я только что реализовал класс 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));
}
}
Если нет требований к синхронизации:
Thread.Sleep(Timeout.Infinite);
Мне нужно пойти и прочитать эту книгу. У меня никогда не было проблем с приостановкой / возобновлением.