Главная » C# » Использование потоков в Visual C# (Sharp)

0

На компьютерах под управлением операционной системы Windows задачи могут выполняться двумя способами: посредством потоков и посредством процессов. Процесс  представляет  собой  исполнение   программы,   для   которой   операциоая  система  выделяет  ресурсы,  включающие,  между  прочих,  и  кванты  времени. А поток — это легковесный процесс, исполняемый в контексте главного процесса.

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

ПРИМЕЧАНИЕ

Дл я общения между процессами можно использовать механизм взаимодействия прессов. Но я бы не рекомендовал делать это, т. к. такой же эффект можно получить с помощь ю домен а приложения  (AppDomain) .NET.   Но  в  большинстве  случаев  прибать к домена м приложений нет надобности. Просто используйте легковесные  (по сравнению с процессом) потоки, которыми легко управлять и которые просто прраммировать.

Создание нового потока

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

Thread threadl = new Thread(

delegate() { Console.WriteLine("hello there"); }); Thread thread2 = new Thread(

() => { Console.WriteLinef"Well then goodbye"); });

threadl. Start () ,- thread2.Start();

Поток порождается созданием экземпляра типа Thread с последующим вызовом метода start О. Класс Thread содержит всю функциональность, необходимую для запуска и управления многозадачности.

При запуске потока, типу Thread требуется код для исполнения. Для этого примяется делегат Threadstart, который передается классу Thread через конструктор. В данном примере тип Threadstart не используется, т. к. в обоих потоках примяются программные конструктивы, не требующие объявления делегата. В первом потоке (threadl) используется анонимный метод, а во втором (thread2) — лямбдыражение. Вызов метода start () запускает исполнение потоком функциональнти анонимного метода или лямбда-выражения.

Результатом исполнения примера потоков может быть следующий вывод:

Well then goodbye hello there

Я говорю "может быть", потому ЧТО hello ВЫВОДИТСЯ перед goodbye. Такой вывод означает, что второй поток (thread2) начал исполняться перед первым потоком (threadl). Но последовательность вывода приветствия и прощания может также быть и в логическом порядке, что демонстрирует природу потоков —  параллелость  и  причину,  почему  программирование потоков является таким трудным.

Представьте   на   секунду,   что   пример   кода   потоков   выполнялся   не   параллельно, а последовательно. В таком случае метод threadl . star t о всегда вызывался бы первым, а метод thread2.start() — вторым, в результате чего мы бы всегда здовались перед прощанием. Последовательное  выполнение  не  представляет  для людей никаких проблем с пониманием. Но представить в уме взаимодействие нкольких задач одновременно — намного более сложная задача. В то время как выполнение параллельных  потоков  не  является  проблемой  для  компьютеров,  логу этого выполнения программируют люди, более привычные к последовательному мышлению, и поэтому создаваемая ими параллельная логика может оказаться сержащей  ошибки.

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

Ожидание завершения исполняющегося потока

Вызов метода star t о  запускает  поток,  таким  образом,  начиная  выполнение  зади. Код, вызвавший поток, не ожидает завершения исполнения  созданного  им  пока, т. к. этот поток является полностью независимым от него. Поэтому при волнении пакетного процесса, нужно будет ждать, пока не завершатся все потоки. Запуск на выполнение нескольких потоков осуществляется  с  помощью  вызываей логики.  Вызывающая  логика  нужно  только  для  запуска  процессов,  поэтому для ее работы требуется очень короткое время, по сравнению со временем для иолнения потоков. Но если завершить выполнение вызывающего потока,  то  также будет принудительно завершено выполнение всех его  дочерних  потоков,  в  резулате  чего  возможно  искажение данных.

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

Thread thread = new Thread(

delegated { Console.WriteLinef"hello there"); Thread.Sleep(2000); }) ;

thread.Start();

thread.Join();

Вызов метода Join о в  последней  строчке  кода  блокирует  вызывающий  поток до тех пор, пока  не  завершится  выполнение  вызванного  им  потока.  Метод Thread, sleep о переводит поток в состояние сна на указанное в параметре время, в данном примере на 2000 миллисекунд или 2 секунды.

Таким образом, проблема преждевременного завершения вызывающего потока решена, но какую пользу приносит ожидание вызывающим потоком завершения созданного потока? В данном примере использование метода Join () не приносит никакой пользы; здесь просто демонстрировалось его применение. Но если вызающий поток выполняет несколько потоков, то прежде чем продолжить свое иолнение, он должен  дождаться  завершения всех этих потоков. Поэтому в ситуии с несколькими выполняющимися потоками следует вызывать метод Join о абсолютно для всех этих потоков.

Вариантом метода join о является указание тайм-аута в параметре. Допустим, что для выполнения обработки запущенному потоку в худшем случае требуется самое большее 5 минут. Тогда, если в течение 5 минут поток не завершит выполнение самостоятельно, то мы завершаем его принудительно. Далее приводится соответсующий код:

if (!thread.Join(300000)) { thread.Abort();

}

В примере вызов метода Joint) заставляет исполняемый поток ожидать в течение 300 000 миллисекунд (5 минут), прежде чем продолжить выполнение. В случае тайм-аута возвращается значение false, и выполнение потока принудительно зершается посредством метода Abort ().

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии