Страницы

Поиск по вопросам

воскресенье, 8 декабря 2019 г.

Завершение потока из другого потока

#c_sharp #многопоточность #c_sharp_faq


Например, есть несколько практически одинаковых потоков, которые могут длиться до
бесконечности

инициализация:

 ParameterizedThreadStart pts = new ParameterizedThreadStart(runMethod);
 Thread t = new Thread(pts);            
 t.Start(values);


поток:

 void run(object p)
 {
      // нужен поток, который повторяет одно и то же с некоторой переодичностью,
поэтому так:

      while(true)
      {
           // что-то делаем
           doAnything(p);

           Thread.Sleep(60000);
      }
 }


Вопрос в том, как их адекватно прервать? Было бы здорово, если их прервать можно
было прервать пока они спят или хотя бы после doAnything();

можно в основном потоке сделать что-то вроде

 Thread t = new Thread(pts);            
 t.Start(values);
 ...
 t.Abort();


но я не уверен, что в потоке не прервется какая-то важная операция - загрузка/удаление/обработка
файла или транзакция какая-то незакомитится
    


Ответы

Ответ 1



Красивее всего это делается через механизм CancellationToken. Этот механизм специально создан для передачи сообщений об остановки и отмене между потоками: void run(object p) { var ct = (CancellationToken)p; do { // что-то делаем doAnything(p); } while (!ct.IsCancellationRequested && !ct.WaitHandle.WaitOne(60000)); } Здесь я использую ожидание на событии как прерываемую альтернативу Thread.Sleep. Если ожидание успешно - значит, поток надо останавливать. Если неуспешно (тайм-аут ожидания) - значит, можно продолжать работу. Перед ожиданием я проверяю IsCancellationRequested, чтобы не создавать событие ядра когда этого не требуется. Создание такого потока: using (var cts = new CancellationTokenSource()) { new Thread(run).Start(cts.Token); ... cts.Cancel(); } Также вместо потока можно использовать задачу (Task.Run). В таком случае для ожидания лучше использовать Task.Delay, это позволит обойтись вовсе без объектов ядра: async Task run(CancellationToken ct) { do { // что-то делаем doAnything(p); await Task.Delay(60000, ct); } while (!ct.IsCancellationRequested); } Task.Run(() => run(cts.Token)); Бонусом к использованию CancellationToken идет возможность передачи ct дальше в doAnything - благодаря чему вы можете тонко выбирать, в какие моменты допустимо прерывание обработки. Если вы будете использовать асинхронный вариант - то практически все асинхронные функции стандартной библиотеки также умеют принимать CancellationToken, что позволяет безопасно прервать любую долгую операцию, если вы того пожелаете. В более старых рантаймах, где механизма CancellationToken нет, для той же цели можно использовать ManualResetEvent. Принцип тот же - вместо вызова Thread.Sleep ждем на событии и проверяем результат.

Ответ 2



Есть более подробный ответ здесь и здесь Самый простой вариант создать boolean переменную, которая отслеживает запрос на завершение потока. Данный вариант не позволяет прервать Thread.Sleep. boolean volatile isRunning = true; void run(object p) { while(true) { if (!isRunning) break; // exit if not running doAnything(p); Thread.Sleep(60000); } } Другой вариант использовать CancellationTokenSource. Подходит начиная с .net 4

Комментариев нет:

Отправить комментарий