Вызов Thread.Abort для потока из ThreadPool - PullRequest
18 голосов
/ 25 февраля 2010

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

Итак, мы хотим использовать страшные Thread.Abort, чтобы убить такие потоки. Я делал это раньше, когда раскручивал свои собственные потоки, но я никогда не использовал ThreadPool. Если мы проследим время начала каждой задачи следующим образом:

static Dictionary<Thread, DateTime> started = new Dictionary<Thread, DateTime>();

static void DoSomeWork(object foo)
{
    lock(started)
        started[Thread.CurrentThread] = DateTime.Now;

    SomeBuggyLibraryThatMightInfiniteLoopOrSomething.callSomeFunction(doo);

    lock(started)
        started.Remove(Thread.CurrentThread);
}

тогда можем ли мы заблокировать и перебрать запущенные потоки и вызвать Thread.Abort, чтобы убить их? И если мы это сделаем, то нам нужно будет добавить новый поток в ThreadPool, чтобы заменить тот, который мы только что убили, или будет обрабатывать это ThreadPool для нас?

РЕДАКТИРОВАТЬ: Я хорошо осведомлен обо всех потенциальных проблемах с Thread.Abort. Я знаю, что в идеале он никогда не должен использоваться в производственном коде, и что он не обязательно даже останавливает поток, и что если вы прерываете поток, когда поток получил блокировку, вы можете повесить другие потоки и т. Д. Но сейчас у нас сжатые сроки и у нас есть веские основания полагать, что в этом конкретном случае мы можем вызвать Thread.Abort, не подвергая риску весь процесс, и мы хотели бы избежать переписывания этой программы, чтобы устранить ThreadPool, если мы не должны.

Итак, я хочу знать следующее: учитывая, что мы будем вызывать Thread.Abort в потоке, принадлежащем ThreadPool, существуют ли какие-либо особые проблемы, вызванные тем, что они являются потоками ThreadPool, и нужно ли нам вручную раскручивать новый поток, чтобы заменить тот, который был убит, или ThreadPool сделает это для нас?

Ответы [ 5 ]

9 голосов
/ 25 февраля 2010

Нет, вы не должны вызывать Abort для потоков в пуле потоков. Из моего локального тестирования кажется, что ThreadPool воссоздает потоки, если вы прервете их - я прервал 1000 потоков пула потоков, и он все еще работал. Я не знаю, стоит ли вам полагаться на это поведение, но, может быть, в этом случае вам это сойдет с рук. В целом, хотя использование Thread.Abort не является правильным способом сделать это.

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

6 голосов
/ 25 февраля 2010

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

Таким образом, хотя обычно не рекомендуется прерывать потоки, существуют узлы, которые очень агрессивно прерывают потоки. Одним из них является ASP.NET. Если запрос занимает слишком много времени, он прерывает поток для вас. Поэтому, имея это в виду, глупо говорить «никогда не прерывать потоки».

Я советую вам выяснить, где этот код висит (трассировка стека ThreadAbortException должна дать вам много информации). Если он всегда зависает в одном и том же месте (это, вероятно, тупик), выясните с помощью Reflector, приведет ли прерывание потока в этой точке к некоторому повреждению состояния. С помощью этой информации вы, возможно, уже можете решить проблему (возможно, вы блокируете объект этой библиотеки) или можете отправить письмо автору этой библиотеки. Если все это не помогает, и вы видите, что нет риска прервать его, будьте прагматичны и убейте его: -)

Однако, если произойдет изменение какой-либо коррупции в государстве, вы должны попытаться ответить ответом Марка Байерса. То есть: попробуйте запустить эту библиотеку в своем собственном домене приложений. Таким образом, вы можете выгрузить весь AppDomain, и в нем нет изменений, влияющих на ваше приложение.

3 голосов
/ 08 июля 2010

Считайте 'Abortable Thread Pool' Стивена Туба. Он предоставляет исходный код для отменяемого пула потоков. Это интересное чтение.

Он вызывает свой собственный обратный вызов 'HandleItem' при постановке в очередь пула потоков. Внутри «HandleItem» он затем выполняет фактический обратный вызов, после добавления текущего потока в список словаря внутри своего класса-оболочки.

ThreadPool.QueueUserWorkItem(new WaitCallback(HandleItem));

HandleItem(...) {
...
_threads.Add(item, Thread.CurrentThread);
...
}

Он использует словарь, чтобы связать свои рабочие элементы с потоками, когда пользователь хочет отменить поток, он ищет и отменяет этот конкретный поток.

Dictionary<WorkItem, Thread>() _threads = New Dictionary<WorkItem, Thread>();

http://msdn.microsoft.com/en-us/magazine/cc163644.aspx

3 голосов
/ 25 февраля 2010

Чтобы уточнить, что говорит Марк, если вы вызываете Thread.Abort, вы не представляете, где он будет прерван в стороннем компоненте, из-за особой природы ThreadAbortException - например, он может оставить FileStream открытым .

Я бы лично создал Threads, на который ссылаются в IList или Queue (так как ThreadPool лучше подходит для fire и забудьте или WaitHandles), и в зависимости от того, думаете ли вы прерывание потока, использующего 3-й партийный компонент не так уж опасен, Abort

Если вы считаете, что прерывание может оставить стороннюю библиотеку в неприемлемом состоянии, Присоединитесь к потокам, которые не завершили один за другим, через определенное время, используя System.Threading.Timer

Альтернативно

Чтобы использовать ThreadPool, вы можете использовать Smart Thread Pool .

1 голос
/ 25 февраля 2010

У вас есть другой вариант (который я выбрал бы, если бы у меня был свободный день):

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

...