Завершение домена приложений

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

Строго говоря, первый шаг – принудительное завершение потока – на самом деле не нужен, так как когда домен приложений выгружается, все потоки, исполняющие код в этом домене, автоматически принудительно завершаются. Однако если потоки при этом не завершаются своевременно (возможно, из-за кода в блоках finally, или по причинам, обсуждавшимся выше), домен приложений не выгружается, и выбрасывается исключение CannotUnloadAppDomainException. По этой причине лучше явно вызвать Abort для рабочих потоков, а затем вызвать Join с заданным таймаутом (который вы можете контролировать) перед выгрузкой домена приложений.

В следующем примере рабочий поток в бесконечном цикле создает и закрывает файл, используя небезопасный в случае аварийного завершения метод File.CreateText. Главный поток занимается тем, что запускает и завершает рабочие потоки. Обычно это заканчивается IOException в течении одной-двух итераций из-за реализации File.CreateText, оставляющей незакрытые файловые хэндлы при аварийном завершении потока:

using System; using System.IO; using System.Threading; class Program { static void Main() { while (true) { Thread t = new Thread(Work); t.Start(); Thread.Sleep(100); t.Abort(); Console.WriteLine("Aborted"); } } static void Work() { while (true) using(StreamWriter w = File.CreateText("myfile.txt")) ... } }

Консольный вывод:

Aborted Aborted System.IO.IOException: The process cannot access the file '...myfile.txt' because it is being used by another process.

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

class Program { static void Main(string [] args) { while (true) { AppDomain ad = AppDomain.CreateDomain("worker"); Thread t = new Thread(delegate() { ad.DoCallBack(Work); }); t.Start(); Thread.Sleep(100); t.Abort(); if (!t.Join(2000)) { // Поток не завершился – можно предпринять что-либо, // если есть что. К счастью, в данном случае можно ожидать, // что поток будет завершаться *всегда*. } AppDomain.Unload(ad); // Выгружаем загрязненный домен! Console.WriteLine("Aborted"); } } static void Work() { while (true) using(StreamWriter w = File.CreateText("myfile.txt")) ... } }

Консольный вывод:

Aborted Aborted Aborted Aborted ...

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: