Как эффективно убить поток в С#?

Я не пытаюсь в избить dead лошадь, честно. И я прочитал все советы по убийству нитей, однако, пожалуйста, рассмотрите код. Он выполняет следующие действия:

  • Он запускает поток (через метод StartThread)
  • Он вызывает базу данных, которая ищет что-либо в очереди ServiceBroker. Обратите внимание на команду WAITFOR - это означает, что она будет сидеть там, пока в очереди не появится что-то. Все это в методе MonitorQueue.
  • Убейте нить. Я пробовал .Interrupt - он ничего не делает. Затем я попробовал .Abort, который никогда не должен использоваться, но даже это ничего не делало.

    Thread thxMonitor = new Thread(MonitorQueue);
    void StartThread() {
        thxMonitor.Start();
    }
    
    void MonitorQueue(object obj) {
        var conn = new SqlConnection(connString);
        conn.Open();
        var cmd = conn.CreateCommand();
        cmd.CommandTimeout = 0; // forever and ever
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = "WAITFOR (RECEIVE CONVERT(int, message_body) AS Message FROM SBQ)";
    
        var dataTable = new DataTable();
        var da = new SqlDataAdapter(command);
    
        da.Fill(dataTable);
        da.Dispose();
    }
    
    void KillThreadByAnyMeansNecessary() {
        thxMonitor.Interrupt();
        thxMonitor.Abort();
    }
    

Действительно ли можно убить поток?

Ответы

Ответ 1

Установите флаг Abort, чтобы сообщить, что поток необходимо завершить. Добавьте фиктивную запись в очередь ServiceBroker. Затем WAITFOR вернется. Затем поток проверяет флаг "Abort" и, находя его набор, удаляет фиктивную запись из очереди и завершает работу.

Другим вариантом было бы добавить "настоящую" запись о ядовитых таблетках в спецификацию таблицы, контролируемой ServiceBroker - незаконным номером записи или тому подобным. Это позволило бы избежать непосредственного касания потока /s - всегда хорошая вещь:) Это может быть более сложным, особенно если каждый рабочий поток проверяется при фактическом завершении, но все равно будет эффективным, если рабочие потоки, ServiceBroker и DB были на разных коробках. Я добавил это как редактирование, потому что, подумав немного больше об этом, он кажется более гибким, в конце концов, если потоки обычно общаются только через. БД, почему бы не закрыть их только с БД? Нет Abort(), no Interrupt() и, надеюсь, нет блокировки, создающей Join().

Ответ 2

Мне не нравится отвечать на ваш вопрос, но подумайте об этом по-другому. T-SQL позволяет указать параметр TIMEOUT с помощью WAITFOR, так что если сообщение не будет получено в течение определенного периода времени, оператор прекратит работу и снова будет проверен. Вы снова видите и снова в шаблонах, где у вас есть ждать. Компромисс заключается в том, что вы не сразу получаете поток, чтобы умереть, когда его просили - вам нужно дождаться истечения вашего тайм-аута до того, как ваш поток погибнет.

Чем быстрее вы хотите, чтобы это произошло, тем меньше ваш интервал ожидания. Хотите, чтобы это произошло мгновенно? Тогда вы должны быть опросом.

static bool _quit = false;

Thread thxMonitor = new Thread(MonitorQueue);
void StartThread() {
    thxMonitor.Start();
}

void MonitorQueue(object obj) {

    var conn = new SqlConnection(connString);
    conn.Open();
    var cmd = conn.CreateCommand();
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "WAITFOR (RECEIVE CONVERT(int, message_body) AS Message FROM SBQ) TIMEOUT 500";

    var dataTable = new DataTable();    

    while(!quit && !dataTable.AsEnumerable().Any()) {
        using (var da = new SqlDataAdapter(command)) {    
            da.Fill(dataTable);
        }
    }
}

void KillThreadByAnyMeansNecessary() {
    _quit = true;
}

ИЗМЕНИТЬ

Хотя это может показаться как опрос очереди, на самом деле это не так. Когда вы проводите опрос, вы активно проверяете что-то, а затем вы ждете, чтобы избежать "вращающегося" состояния, когда вы постоянно сжигаете CPU (хотя иногда вы даже не дожидаетесь).

Рассмотрим, что происходит в сценарии опроса, когда вы проверяете записи, а затем подождите 500 мс. Если ничего в очереди и 200 мс позже не поступит сообщение, вам нужно подождать еще 300 мс при опросе, чтобы получить сообщение. С таймаутом, если сообщение достигает 200 мс в тайм-аут метода "wait", сообщение обрабатывается немедленно.

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

Ответ 3

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

после истечения таймаута, проверьте, был ли поток прерван.

если нет, обведите назад и снова подождите.

Да, "вся точка" waitfor - это что-то ждать. Но если вы хотите, чтобы что-то было отзывчивым, вы не можете просить один поток ждать Infinity, а затем ожидать, что он будет слушать что-нибудь еще.

Ответ 4

Не делай этого! Серьезно!

Функция, которую нужно вызвать, чтобы убить поток, - это функция TerminateThread, которую вы можете вызвать через P/Invoke. Все причины, почему вы не должны использовать этот метод, указаны в документации

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

  • Если целевой поток принадлежит критическому разделу, критический раздел не будет освобожден.
  • Если целевой поток выделяет память из кучи, блокировка кучи не будет выпущена.
  • Если целевой поток выполняет определенные вызовы ядра32, когда он завершен, состояние kernel32 для процесса потока может быть противоречива.
  • Если целевой поток управляет глобальным состоянием общей DLL, состояние DLL может быть уничтожено, что влияет на другие пользователей DLL.

Важно отметить, что бит выделен жирным шрифтом и тот факт, что в рамках среды CLR/.Net вы никогда в ситуации, когда вы точно знаете, что делает целевой поток (если только вы случайно написали CLR).

Чтобы уточнить, вызов TerminateThread в потоке, запущенном. Net-кодом, вполне может затормозить ваш процесс или иным образом уйти в полностью невосстановимое состояние.

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


Метод Thread.Abort немного более безопасен, поскольку он вызывает ThreadAbortException, а не сразу же срывает ваш поток, однако это имеет Недостаток не всегда работает - CLR может генерировать исключение только в том случае, если CLR на самом деле запускает код в этом потоке, однако в этом случае поток, вероятно, сидит, ожидая, что некоторый запрос ввода-вывода будет выполнен в собственном коде клиента SQL Server, почему ваш вызов Thread.Abort ничего не делает и ничего не сделает, пока управление не будет возвращено в CLR.

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

Ответ 5

Не просто сразу закрыть поток. С ним связана потенциальная потенциальная проблема:

Ваша нить получает блокировку, а затем вы ее убиваете, прежде чем она освободит блокировку. Теперь потоки, требующие блокировки, застрянут.

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

Пожалуйста, обратитесь к этому вопросу, обсуждая одно и то же: Как убить поток мгновенно на С#?