Урок 10. Программирование многопоточности

4. Завершение потоков

Когда вы вызываете Thread. Abort, рассматриваемый поток, в конце концов, получает ThreadAbortException. Поэтому, естественно, для аккуратной обработки этой ситуации вы должны обрабатывать исключение ThreadAbortException, если ваш поток должен делать что-то особенное в случае его прерывания. Существует даже перегрузка Abort, которая принимает произвольную объектную ссылку, инкапсулируемую затем в ThreadAbortException, это позволяет коду, прерывающему поток, передать некоторую контекстную информацию обработчику ThreadAbortException, например, причину вызова Abort.

CLR не доставляет ThreadAbortException, если только поток не выполняется внутри управляемого контекста. Если ваш поток вызвал "родную" функцию через слой P/Invoke, и эта функция требует длительного времени для завершения, то прерывание потока откладывается до тех пор, пока управление не вернется в управляемое пространство.

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

using System;
using System. Threading;
using System. Collections. Generic;
using System. Linq;
using System. Text;
namespace MnogoPotochnost3
{
public class EntryPoint
{
private static void ThreadFunc()
{
ulong counter = 0;
while ( true )
{
try
{
Console. WriteLine( "{0}" , counter++);
}
catch ( ThreadAbortException )
{
//Попытка проглотить исключение и продолжиться.
Console. WriteLine( "Abort!" );
}
}
}
static void Main( string [] args)
{
Thead newThread = new Thread ( new ThreadStart ( EntryPoint. ThreadFunc));
newThread. Start();
Thread. Sleep(2000);
// Прервать поток.
newThead. Join();
}
}
}

После беглого взгляда на этот код может показаться, что вызов Join на экземпляре newInstance заблокирует выполнение навсегда. Однако этого не случается. Казалось бы, поскольку ThreadAbortException обрабатывается внутри цикла фyнкции потока, исключение будет "проглочено"и цикл продолжится, независимо от того, сколько раз главный поток попытается прервать данный поток. Однако ThreadAbortException, сгенерированное через метод Thread. Abort ведет себя особым образом. Когда ваш поток завершает обработку исключения, исполняющая система заново неявно генерирует его в конце вашего обработчика. Это все равно, как если бы вы сами повторно сгенерировали исключение. Таким образом, любые внешние обработчики или блоки finally будут выполняться нормально. В примере вызов Join не будет ждать вечно, как можно было предполагать.

Можно предотвратить повторную генерацию ThreadAbortException системой вызовом статического метода Thread. ResetAbort. Однако общая рекомендация состоит в том, чтоб вы вызывали только ResetAbort из потока, вызвавшего Abort, это потребовало бы некоторых ухищрений и сложной техники взаимодействия, если бы вы захотели сделать то же самое изнутри обработчика исключения ThreadAbortException прерываемого потока. Если вы пришли к заключению о необходимости реализации такой техники отмены прерывания потока, то это, скорее всего, свидетельствует о том, что вам, прежде всего, стоит пересмотреть дизайн своей системы. Другими словами, это признак плохого дизайна!

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