не хватает одного параметра: static void Main(string[] args) |
Потоки и параллельные вычисления
Свойства потоков
Рассмотрим некоторые свойства объектов класса Thread.
Свойство Name позволяет узнать имя потока или задать потоку собственное имя, отличное от служебного имени, получаемого потоком в момент создания. Именование потоков облегчает процесс отладки многопоточного приложения.
Свойство Priority позволяет задавать и получать приоритет потока. Возможные 5 значений этого свойства задаются перечислением ThreadPriority. По умолчанию поток получает приоритет, заданный значением Normal. Можно задать для приоритета два значения ниже приоритета Normal - BelowNormal и Lowest. Можно задать для приоритета два значения выше приоритета Normal - AboveNormal и Highest.
Среди других свойств отметим группу свойств Current, позволяющих получить текущий поток, текущий контекст потока, текущую культуру, используемую потоком. Другая группа свойств Is позволяет выяснить различные характеристики потока - его состояние, является ли он фоновым или членом группы потоков.
Завершение потока
Помимо уже упомянутых методов Start, Join, Sleep в классе Thread есть еще несколько десятков методов, как статических, так и динамических, вызываемых объектами - экземплярами класса Thread. Некоторые из этих методов появятся в примерах.
Сейчас же рассмотрим два метода, позволяющие завершить поток - Interrupt и Abort.
Когда поток вызывает метод Interrupt, то все зависит от того, в каком состоянии находится поток. Если поток находится в заблокированном состоянии или через некоторое время перейдет в заблокированное состояние, например, "уснет", то поток выводится из этого состояния. Вот как это происходит. Для заблокированного потока возникает исключительная ситуация ThreadInterruptedException. Управление будет передано обработчику этой ситуации и по его завершению, завершит свою жизнь и поток. Понятно, что вызов Interrupt предполагает, что предусмотрен обработчик такой ситуации. В противном случае работа приложения будет прервана, что конечно является печальным фактом.
Когда поток вызывает метод Abort, то независимо от того, в каком состоянии находится поток, он обычно завершается. При этом также возникает исключительная ситуация ThreadAbortException, для которой можно предусмотреть обработчик события. Но даже если этого обработчика нет, поток нормально завершит свою работу. В отдельных случаях при обработке исключения можно отменить уничтожение потока, вызвав метод ResetAbort.
У прерываемого потока может быть блок finally, который выполняется до прерывания потока. Поскольку завершение потока это некоторый процесс, требующий времени, то вызывающий поток, прерывающий работу дочернего потока, после вызова метода Abort вызывает метод Join, чтобы дождаться завершения потока.
Создание и завершение потока
Рассмотрим пример, иллюстрирующий создание потоков и их принудительное завершение с использованием методов Abort и Interrupt.
Создадим консольный проект с именем ConsoleCounter. Как и положено, для методов, выполняемых в потоках, создадим класс, который назовем Counts. Класс устроен просто, у него одно закрытое поле, играющее роль счетчика, и константа, задающая максимальное значение счетчика:
/// <summary> /// Счетчики /// </summary> class Counts { long sum = 0; const long LIMIT = 100000000; public long Sum { get { return sum; } } }
Добавим в класс метод Count, увеличивающий значение счетчика:
/// <summary> /// Счетчик, в цикле увеличивается, /// пока не достигнет значения LIMIT /// Метод ленивый - засыпает на каждом шагу, /// </summary> public void Count() { sum = 0; try { while (sum < LIMIT) { sum++; Thread.Sleep(0); } } catch (ThreadInterruptedException ) { Console.WriteLine("Метод Count был прерван вызовом Interrupt!"); } finally { Console.WriteLine("Конец - делу венец!"); } }
Метод на каждом шагу засыпает, так что его выполнение в потоке блокируется, и он периодически будет переходить в состояние "ожидание". Метод предусматривает обработку события Interrupt.
А теперь в процедуре main нашего приложения создадим поток, выполняющий метод Count:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleCounter { class Program { static void Main(string[] args) { //Создание и запуск потока Thread thread_count; Counts my_counter = new Counts(); thread_count = new Thread(my_counter.Count); thread_count.Start(); //Засыпаем, дав потоку возможность поработать Thread.Sleep(1); //Просыпаемся и останавливаем работу потока thread_count.Interrupt(); //Дожидаемся завершения потока thread_count.Join(); //Печать результатов работы потока Console.WriteLine("count = " + my_counter.Sum); } } }
Обратите внимание, вначале создается объект my_counter класса Counts, а затем этот объект, вызывающий метод Count передается конструктору класса Thread при создании потока thread_count. Что делается в процедуре main после создания и запуска потока? Основной поток засыпает, давая возможность поработать дочернему потоку, но, проснувшись, прерывает работу дочернего потока, который мог и не успеть закончить свою работу. Запустив этот проект на выполнение на своем одноядерном компьютере, я получил следующие результаты:
Понятно, что произошло. Поток thread_count часто "засыпал". Когда в таком состоянии основной поток вызвал метод Interrupt, то возникла исключительная ситуация - ThreadInterruptedException, которая была обработана, после чего поток выполнил действия, указанные в блоке finally и завершил свою работу, успев немного посчитать.
Что произойдет, если поток засыпать не будет? Давайте в методе Count закомментируем оператор
Thread.Sleep(0);
Поскольку до завершения своей работы поток thread_count не блокируется, то вызов Interrupt не оказывает на него никакого воздействия, и он спокойно досчитает до максимально возможного значения LIMIT.
А что произойдет, если поток блокируется, но обработчик события ThreadInterruptedException не предусмотрен? Закомментируем обработчик события в методе Count. Для потока в заблокированном состоянии возникнет исключительная ситуация и обрабатывать ее будет системный обработчик, прерывающий работу приложения. Блок finally и в этом случае будет работать.
Запуская этот проект на другом своем компьютере, у которого четыре ядра, получаю аналогичные результаты за тем небольшим исключением, что счетчик больше насчитает.
Для проверки работы метода завершения потока Abort добавим в класс Counts метод Count2, похожий на метод Count, но устроенный чуть сложнее, поскольку он восстанавливает свою работу в результате обработки исключительной ситуации:
/// <summary> /// Счетчик, в цикле увеличивается, /// пока не достигнет значения LIMIT /// Метод упорный - завершает счет, /// несмотря на попытку прерывания при вызове Abort /// </summary> public void Count2() { try { while (sum < LIMIT) { sum += 2; } } catch (ThreadAbortException) { Console.WriteLine("Не буду завершать Count2 при вызове Abort" + "\r\n" + "Восстановлюсь и завершу свою работу!!"); Thread.ResetAbort(); } finally { Console.WriteLine("Конец - делу венец!"); } while (sum < LIMIT) { sum += 2; } }
Обратите внимание, после завершения обработчика события Abort продолжается увеличение счетчика.
Добавим теперь в процедуру main создание второго потока, выполняющего метод Count2:
//Создание второго потока Thread thread_count2; Counts my_counter2 = new Counts(); thread_count2 = new Thread(my_counter2.Count2); thread_count2.Start(); Thread.Sleep(1); thread_count2.Abort(); thread_count2.Join(); Console.WriteLine("count2 = " + my_counter2.Sum);
Заметьте, здесь для прекращения работы потока вызывается метод Abort. У потока возникает исключительная ситуация, хотя он и не блокируется, находясь в состоянии "выполнение". В данном примере у метода Count2 предусмотрен обработчик этой ситуации, который отменяет попытку прекращения работы потока, так что последний завершает выполнение своей работы. Вот как выглядят результаты работы в этой ситуации:
Что произойдет, если в обработчике события не вызывать метод ResetAbort, отменяющий прерывание работы потока? Если закомментировать этот оператор, то работа потока будет прервана.
Более того, можно вообще отключить обработчик события! И в этом случае поток прервет свою работу без всяких осложнений, никакой системный обработчик события вызываться не будет. Прерывание работы потока будет происходить независимо от того, будет ли поток блокирован или находится в состоянии "выполнение".