Вопрос о прекращении потока в .NET. - программирование
Подтвердить что ты не робот

Вопрос о прекращении потока в .NET.

Я понимаю, что Thread.Abort() является злом из множества статей, которые я прочитал по этой теме, поэтому в настоящее время я нахожусь в процессе копирования моего прерывания, чтобы заменить его более чистым способом; и после сравнения пользовательских стратегий с людьми здесь, в stackoverflow, а затем после чтения " Как создавать и завершать потоки (руководство по программированию на С#)" из MSDN, оба из которых утверждают, что подход очень одинаков - это использовать стратегию проверки подхода volatile bool, что приятно, но у меня все еще есть несколько вопросов....

Сразу же то, что выделяется мне здесь, - это то, что если у вас нет простого рабочего процесса, который просто запускает цикл хрустящего кода? Например, для меня мой процесс - это процесс загрузки фонового файла, я на самом деле прохожу через каждый файл, поэтому что-то, и я уверен, что могу добавить мой while (!_shouldStop) вверху, который покрывает меня каждую итерацию цикла, но у меня много больше бизнес-процессов, которые происходят до того, как он ударит по следующей итерации цикла, я хочу, чтобы эта процедура отмены была быстрой; не говорите мне, что мне нужно посыпать их, а петли каждые 4-5 строк вниз по всей моей рабочей функции?!

Я действительно надеюсь, что есть лучший способ, может кто-нибудь, пожалуйста, посоветуйте мне, если это на самом деле, правильный [и только?] подход для этого или стратегии, которые они использовали в прошлом, чтобы достичь того, что я получаю после.

Спасибо банде.

Дальнейшее чтение: Все эти ответы SO предполагают, что рабочий поток будет циклическим. Это не устраивает меня со мной. Что делать, если это линейная, но своевременная фоновая операция?

4b9b3361

Ответ 1

К сожалению, не может быть лучшего варианта. Это зависит от вашего конкретного сценария. Идея состоит в том, чтобы остановить поток изящно в безопасных точках. В этом суть причины, почему Thread.Abort не является хорошим; потому что это не гарантируется в безопасных точках. Посыпая код механизмом остановки, вы эффективно определяете безопасные точки вручную. Это называется совместной отменой. Для этого есть в основном 4 широких механизма. Вы можете выбрать тот, который наилучшим образом соответствует вашей ситуации.

Опросить флаг остановки

Вы уже упоминали этот метод. Это довольно распространенный вопрос. Периодически проверяйте флаг в безопасных точках вашего алгоритма и выходите из системы, когда он получает сигнал. Стандартным подходом является отметка переменной volatile. Если это невозможно или неудобно, вы можете использовать lock. Помните, вы не можете пометить локальную переменную как volatile, поэтому, если выражение лямбда захватывает его посредством закрытия, например, вам придется прибегнуть к другому методу для создания требуемого барьера памяти. Для этого метода не требуется много другого.

Использовать новые механизмы отмены в TPL

Это похоже на опрос флаг остановки, за исключением того, что он использует новые структуры данных отмены в TPL. Он по-прежнему основан на совлокальных моделях аннулирования. Вам нужно получить CancellationToken и периодически проверять IsCancellationRequested. Чтобы запросить отмену, вы вызываете Cancel на CancellationTokenSource, который первоначально предоставил токен. С новыми механизмами отмены вы можете многое сделать. Вы можете узнать больше о здесь.

Использовать механизмы ожидания

Этот метод может быть полезен, если ваш рабочий поток требует ожидания на определенном интервале или для сигнала во время его нормальной работы. Например, вы можете Set a ManualResetEvent, чтобы поток знал, что пришло время остановиться. Вы можете протестировать событие, используя функцию WaitOne, которая возвращает bool, указывающую, было ли событие сигнализировано. WaitOne принимает параметр, который указывает, сколько времени ждать, пока вызов не будет возвращен, если событие не было сигнализировано за такое количество времени. Вы можете использовать эту технику вместо Thread.Sleep и получить индикацию остановки одновременно. Это также полезно, если есть другие экземпляры WaitHandle, которые может потребоваться для потока. Вы можете вызвать WaitHandle.WaitAny, чтобы ждать в любом событии (включая событие остановки) всего за один вызов. Использование события может быть лучше, чем вызов Thread.Interrupt, поскольку у вас больше контроля над потоком программы (Thread.Interrupt генерирует исключение, поэтому вам нужно стратегически разместить блоки try-catch для выполнения любой необходимой очистки).

Специализированные сценарии

Существует несколько одноразовых сценариев, которые имеют очень специализированные механизмы остановки. Это определенно выходит за рамки этого ответа, чтобы перечислить их всех (неважно, что это было бы почти невозможно). Хорошим примером того, что я имею в виду здесь, является класс Socket. Если поток заблокирован при вызове Send или Receive, то вызов Close прерывает сокет при любом блокирующем вызове, который он фактически разблокировал. Я уверен, что в BCL есть несколько других областей, где для разблокировки потока можно использовать аналогичные методы.

Прервать поток через Thread.Interrupt

Преимущество в том, что это просто, и вам не нужно сосредотачиваться на том, чтобы посыпать ваш код чем-нибудь действительно. Недостатком является то, что у вас мало контроля над тем, где безопасные точки находятся в вашем алгоритме. Причина в том, что Thread.Interrupt работает, введя исключение внутри одного из запрещенных вызовов BCL. К ним относятся Thread.Sleep, WaitHandle.WaitOne, Thread.Join и т.д. Поэтому вам нужно быть мудрым относительно того, где вы их размещаете. Однако в большинстве случаев алгоритм определяет, куда они идут, и обычно это нормально, особенно если ваш алгоритм проводит большую часть своего времени в одном из этих блокирующих вызовов. Если алгоритм не использует один из блокирующих вызовов в BCL, этот метод не будет работать для вас. Теория здесь заключается в том, что ThreadInterruptException генерируется только из ожидающего вызова .NET, поэтому он, вероятно, находится в безопасном месте. По крайней мере, вы знаете, что нить не может находиться в неуправляемом коде или выходить из критической секции, оставляя висячий замок в приобретенном состоянии. Несмотря на то, что это менее агрессивно, чем Thread.Abort, я все еще препятствую его использованию, потому что неясно, какие вызовы реагируют на него, и многие разработчики будут не знакомы с его нюансами.

Ответ 2

Ну, к сожалению, в многопоточности вам часто приходится компрометировать "привязанность" к чистоте... вы можете немедленно выйти из потока, если вы Interrupt, но это будет не очень чисто. Так что нет, вам не нужно посыпать теги _shouldStop каждые 4-5 строк, но если вы прервите свой поток, вы должны обработать исключение и выйти из цикла в чистом виде.

Update

Даже если это не циклический поток (т.е. возможно, это поток, который выполняет некоторую длительную асинхронную операцию или какой-то блок для операции ввода), вы можете Interrupt его, но вы все равно должны поймать ThreadInterruptedException и выйдите из потока чисто. Я думаю, что примеры, которые вы читали, очень уместны.

Обновление 2.0

Да У меня есть пример... Я просто покажу вам пример, основанный на ссылке, на которую вы ссылались:

public class InterruptExample
{
    private Thread t;
    private volatile boolean alive;

    public InterruptExample()
    {
        alive = false;

        t = new Thread(()=>
        {
            try
            {
                while (alive)
                {
                    /* Do work. */
                }
            }
            catch (ThreadInterruptedException exception)
            {
                /* Clean up. */
            }
        });
        t.IsBackground = true;
    }

    public void Start()
    {
        alive = true;
        t.Start();
    }


    public void Kill(int timeout = 0)
    {
        // somebody tells you to stop the thread
        t.Interrupt();

        // Optionally you can block the caller
        // by making them wait until the thread exits.
        // If they leave the default timeout, 
        // then they will not wait at all
        t.Join(timeout);
    }
}

Ответ 3

Если отмена требуется для вещи, которую вы строите, тогда ее следует рассматривать с таким же уважением, как и остальная часть вашего кода, - это может быть что-то, что вам нужно разработать.

Предположим, что ваш поток выполняет одну из двух задач в любое время.

  • Что-то связанное с процессором
  • Ожидание ядра

Если вы привязаны к процессору в рассматриваемом потоке, у вас, вероятно, есть хорошее место, чтобы вставить проверку выкупа. Если вы вызываете код кому-то еще для выполнения долговременной задачи, связанной с процессором, тогда вам может понадобиться исправить внешний код, вывести его из процесса (прерывание потоков является злым, но прерывистые процессы четко определены и безопасны ) и т.д.

Если вы ожидаете ядро, тогда, вероятно, в ожидании будет задействован дескриптор (или fd, или mach port,...). Обычно, если вы уничтожаете соответствующий дескриптор, ядро ​​немедленно вернется с некоторым кодом ошибки. Если вы находитесь в .net/java/etc. вы, скорее всего, закончите исключение. В C любой код, который у вас уже есть для обработки сбоев системного вызова, будет распространять ошибку до значимой части вашего приложения. В любом случае, вы выходите из низкоуровневого места довольно чисто и очень своевременно, без необходимости использования нового кода повсюду.

Тактика, которую я часто использую с таким кодом, заключается в том, чтобы отслеживать список ручек, которые нужно закрыть, а затем моя функция прерывания установила флаг "отменена", а затем закроет их. Когда функция выходит из строя, она может проверять флаг и сообщение об ошибке из-за отмены, а не из-за того, что было для конкретного исключения /errno.

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

  • Он избегает класса условий гонки, когда старые ветки bgwork оживают после неожиданных задержек.

  • Это позволяет избежать скрытых утечек потока/памяти, вызванных зависанием фоновых процессов, позволяя скрывать эффекты висячего фонового потока.

Есть две причины бояться такого подхода:

  • Вы не думаете, что можете своевременно прервать свой собственный код. Если отмена является требованием вашего приложения, решение, которое вам действительно нужно сделать, - это решение для ресурсов/бизнеса: сделать взломать или исправить вашу проблему чисто.

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

Ответ 4

Возможно, часть проблемы состоит в том, что у вас такой длинный цикл method/while. Независимо от того, имеете ли вы проблемы с потоками, вы должны разбить его на более мелкие этапы обработки. Предположим, что этими шагами являются Alpha(), Bravo(), Charlie() и Delta().

Затем вы можете сделать что-то вроде этого:

    public void MyBigBackgroundTask()
    {
        Action[] tasks = new Action[] { Alpha, Bravo, Charlie, Delta };
        int workStepSize = 0;
        while (!_shouldStop)
        {
            tasks[workStepSize++]();
            workStepSize %= tasks.Length;
        };
    }

Итак, да, он бесконечно циклически, но проверяет, не пора ли останавливаться между каждым бизнес-шагом.

Ответ 5

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

Если у вас есть прямая резьба "идти делать что-то и закрывать" (в ней нет петель), вы просто проверяете _shouldStop логическое значение до или после каждого основного места внутри потока. Таким образом, вы знаете, следует ли продолжать или выручать.

например:

public void DoWork() {

  RunSomeBigMethod();
  if (_shouldStop){ return; }
  RunSomeOtherBigMethod();
  if (_shouldStop){ return; }
  //....
}

Ответ 6

Лучший ответ во многом зависит от того, что вы делаете в потоке.

  • Как вы сказали, большинство ответов вращается вокруг опроса общих логических строк каждой пары. Даже если вам это может не понравиться, это часто самая простая схема. Если вы хотите сделать вашу жизнь проще, вы можете написать такой метод, как ThrowIfCancelled(), который выдает какое-то исключение, если вы закончите. Пуристы скажут, что это (вздох) с использованием исключений для потока управления, но затем снова каселлинг является исключительным imo.

  • Если вы выполняете операции ввода-вывода (например, сетевые элементы), вам может потребоваться сделать все с помощью асинхронных операций.

  • Если вы выполняете последовательность шагов, вы можете использовать трюк IEnumerable для создания конечного автомата. Пример:

<

abstract class StateMachine : IDisposable
{
    public abstract IEnumerable<object> Main();

    public virtual void Dispose()
    {
        /// ... override with free-ing code ...
   }

   bool wasCancelled;

   public bool Cancel()
   {
     // ... set wasCancelled using locking scheme of choice ...
    }

   public Thread Run()
   {
       var thread = new Thread(() =>
           {
              try
              {
                if(wasCancelled) return;
                foreach(var x in Main())
                {
                    if(wasCancelled) return;
                }
              }
              finally { Dispose(); }
           });
       thread.Start()
   }
}

class MyStateMachine : StateMachine
{
   public override IEnumerabl<object> Main()
   {
       DoSomething();
       yield return null;
       DoSomethingElse();
       yield return null;
   }
}

// then call new MyStateMachine().Run() to run.
<Р →

переустройство? Это зависит от того, сколько государственных машин вы используете. Если у вас всего 1, да. Если у вас 100, то, возможно, нет. Слишком сложно? Смотря как. Еще одним преимуществом такого подхода является то, что он позволяет вам (с небольшими изменениями) перевести вашу операцию в обратный вызов Timer.tick и пустую нить, если это имеет смысл.

и делать все, что говорит blucz.

Ответ 7

Вместо добавления цикла while, в котором цикл не принадлежит другим, добавьте что-то вроде if (_shouldStop) CleanupAndExit(); везде, где это имеет смысл. Там нет необходимости проверять после каждой операции или полностью посыпать код. Вместо этого подумайте о каждой проверке как о возможности выхода из потока в этот момент и добавьте их стратегически с учетом этого.

Ответ 8

Все эти ответы SO предполагают, что рабочий поток будет циклическим. Это не устраивает меня со мной.

Существует много способов сделать код длительным. Looping - довольно важная конструкция программирования. Выполнение кода занимает много времени без циклов, принимает огромное количество заявлений. Сотни тысяч.

Или вызов другого кода, который выполняет цикл для вас. Да, сложно сделать этот код по требованию. Это просто не работает.