программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Скачать 3.32 Mb.
|
Console.WriteLine(Thrd.Name + " начат."); do { Thread.Sleep (500); Console.WriteLine("В потоке " + Thrd.Name + ", Count = " + Count); Count++; } while(Count < 10); Console.WriteLine(Thrd.Name + " завершен."); } } class MoreThreads { static void Main() { Console.WriteLine("Основной поток начат."); // Сконструировать три потока. do { Console.Write("."); Thread.Sleep(100) ; } while (mtl.Count <10 I | mt2.Count <10 || mt3.Count < 10); Console.WriteLine("Основной поток завершен."); } } Ниже приведен один из возможных результатов выполнения этой программы Основной поток начат. .Потомок #1 начат. Потомок #2 начат. Потомок #3 начат. ....В потоке Потомок #1, Count = 0 В потоке Потомок #2, Count = 0 В потоке Потомок #3, Count = 0 .....В потоке Потомок #1, Count = 1 Поток #1 завершен. В потоке Потомок #2, Count = 9 Поток #2 завершен. В потоке Потомок #3, Count = 9 Поток #3 завершен. Основной поток завершен. Как видите, после того как все три потока начнут выполняться, они будут совместно использовать ЦП. Приведенный выше результат может отличаться в зависимости от среды выполнения, операционной системы и других внешних факторов, влияющих на выполнение программы. Определение момента окончания потока Нередко оказывается полезно знать, когда именно завершается поток. В предыдущих примерах программ для этой цели отслеживалось значение переменной Count. Но ведь это далеко не лучшее и не совсем пригодное для обобщения решение. Правда, в классе Thread имеются два других средства для определения момента окончания потока. С этой целью можно, прежде всего, опросить доступное только для чтения свойство Is Alive, определяемое следующим образом. public bool IsAlive { get; } Свойство IsAlive возвращает логическое значение true, если поток, для которого оно вызывается, по‑прежнему выполняется. Для "опробования" свойства IsAlive подставьте приведенный ниже фрагмент кода вместо кода в классе More Thread из предыдущей версии многопоточной программы, как показано ниже. // Использовать свойство IsAlive для отслеживания момента окончания потоков, class MoreThreads { static void Main() { Console.WriteLine("Основной поток начат."); // Сконструировать три потока. do { Console.Write("."); Thread.Sleep(100); } while (mtl.Thrd.IsAlive && mt2.Thrd.IsAlive && mt3.Thrd.IsAlive); Console.WriteLine("Основной поток завершен."); } } При выполнении этой версии программы результат получается таким же, как и прежде. Единственное отличие заключается в том, что в ней используется свойство IsAlive для отслеживания момента окончания порожденных потоков. Еще один способ отслеживания момента окончания состоит в вызове метода Join (). Ниже приведена его простейшая форма. public void Join() Метод Join () ожидает до тех пор, пока поток, для которого он был вызван, не завершится. Его имя отражает принцип ожидания до тех пор, пока вызывающий поток не присоединится к вызванному методу. Если же данный поток не был начат, то генерируется исключение ThreadStateException. В других формах метода Join () можно указать максимальный период времени, в течение которого следует ожидать завершения указанного потока. В приведенном ниже примере программы метод Join () используется для того, чтобы основной поток завершился последним. // Использовать метод Join(). using System; using System.Threading; class MyThread { public int Count; public Thread Thrd; public MyThread(string name) { Count = 0; Thrd = new Thread(this.Run); Thrd.Name = name; Thrd.Start (); } // Точка входа в поток, void Run() { Console.WriteLine(Thrd.Name + " начат."); do { Thread.Sleep(500); Console .WriteLine ("В потоке " + Thrd.Name + ", Count = " + Count); Count++; } Vhile(Count < 10); Console.WriteLine(Thrd.Name + " завершен."); } } // Использовать метод Join() для ожидания до тех пор, // пока потоки не завершатся, class JoinThreads { static void Main() { Console.WriteLine("Основной поток начат."); // Сконструировать три потока. mtl.Thrd.Join(); Console.WriteLine("Потомок #1 присоединен."); mt2.Thrd.Join(); Console.WriteLine("Потомок #2 присоединен."); mt3.Thrd.Join(); Console.WriteLine("Потомок #3 присоединен."); Console.WriteLine("Основной поток завершен."); } } Ниже приведен один из возможных результатов выполнения этой программы. Напомним, что он может отличаться в зависимости от среды выполнения, операционной системы и прочих факторов, влияющих на выполнение программы. Основной поток начат. Потомок #1 начат. • Потомок #2 начат. Потомок #3 начат. в потоке Потомок #3, Count = 3 в потоке Потомок #1, Count = 4 в потоке Потомок #2, Count = 4 в потоке Потомок #3, Count = 4 в потоке Потомок #1, Count = 5 в потоке Потомок #2 , Count = 5 в потоке Потомок #3, Count = 5 в потоке Потомок #1, Count = 6 в потоке Потомок #2 , Count = 6 в потоке Потомок #3, Count = 6 в потоке Потомок #1, Count = 7 в потоке Потомок #2, Count = 7 в потоке Потомок #3, Count = 7 в потоке Потомок #1/ Count = 8 в потоке Потомок #2, Count = 8 в потоке Потомок #3, Count = 8 в потоке Потомок #1/ Count = 9 Потомок #1 завершен. В потоке Потомок #2, Count = 9 Потомок #2 завершен. В потоке Потомок #3, Count = 9 Потомок #3 завершен. Потомок #1 присоединен. Потомок #2 присоединен. Потомок #3 присоединен. Основной поток завершен. Как видите, выполнение потоков завершилось после возврата из последовательного ряда вызовов метода Join (). Передача аргумента потоку Первоначально в среде .NET Framework нельзя было передавать аргумент потоку, когда он начинался, поскольку у метода, служившего в качестве точки входа в поток, не могло быть параметров. Если же потоку требовалось передать какую‑то информацию, то к этой цели приходилось идти различными обходными путями, например использовать общую переменную. Но этот недостаток был впоследствии устранен, и теперь аргумент может быть передан потоку. Для этого придется воспользоваться другими формами метода Start () , конструктора класса Thread, а также метода, служащего в качестве точки входа в поток. Аргумент передается потоку в следующей форме метода Start (). public void Start(object параметр) Объект, указываемый в качестве аргумента параметр , автоматически передается методу, выполняющему роль точки входа в поток. Следовательно, для того чтобы передать аргумент потоку, достаточно передать его методу Start (). Для применения параметризированной формы метода Start () потребуется следующая форма конструктора класса Thread: public Thread(ParameterizedThreadStart запуск) где запуск обозначает метод, вызываемый с целью начать выполнение потока. Обратите внимание на то, что в этой форме конструктора запуск имеет тип ParameterizedThreadStart, а не ThreadStart, как в форме, использовавшейся в предыдущих примерах. В данном случае Parameter izedThreadS tart является делегатом, объявляемым следующим образом. public delegate void ParameterizedThreadStart(object obj) Как видите, этот делегат принимает аргумент типа obj ect. Поэтому для правильного применения данной формы конструктора класса Thread у метода, служащего в качестве точки входа в поток, должен быть параметр типа obj ect. В приведенном ниже примере программы демонстрируется передача аргумента потоку. // Пример передачи аргумента методу потока. using System; using System.Threading; class MyThread { public int Count; public Thread Thrd; // Обратите внимание на то, что конструктору класса // MyThread передается также значение типа int. public MyThread(string name, int num) { Count = 0; // Вызвать конструктор типа ParameterizedThreadStart // явным образом только ради наглядности примера. Thrd = new Thread(this.Run); Thrd.Name = name; // Здесь переменная num передается методу Start () // в качестве аргумента. Thrd.Start(num); } // Обратите внимание на то, что в этой форме метода Run() // указывается параметр типа object. ’ void Run(object num) { Console.WriteLine(Thrd.Name + " начат со счета " + num); do { Thread.Sleep (500); Console.WriteLine("В потоке " + Thrd.Name + ", Count = " + Count); Count++; } while(Count < (int) num); Console.WriteLine(Thrd.Name + " завершен."); } } class PassArgDemo { static void Main() { // Обратите внимание на то, что число повторений // передается этим двум объектам типа MyThread. MyThread mt = new MyThread("Потомок #1", 5); MyThread mt2 = new MyThread("Потомок #2", 3); do { Thread.Sleep(100); } while (mt.Thrd.IsAlive'| mt2.Thrd.IsAlive); Console.WriteLine("Основной поток завершен."); } } Ниже приведен результат выполнения данной программы, хотя у вас он может оказаться несколько иным. Потомок #1 завершен. Основной поток завершен. Как следует из приведенного выше результата, первый поток повторяется пять раз, а второй – три раза. Число повторений указывается в конструкторе класса MyThread и затем передается методу Run ( ), служащему в качестве точки входа в поток, с помощью параметризированной формы ParameterizedThreadStart метода Start (). Свойство IsBackground Как упоминалось выше, в среде .NET Framework определены две разновидности потоков: приоритетный и фоновый. Единственное отличие между ними заключается в том, что процесс не завершится до тех пор, пока не окончится приоритетный поток, тогда как фоновые потоки завершаются автоматически по окончании всех приоритетных потоков. По умолчанию создаваемый поток становится приоритетным. Но его можно сделать фоновым, используя свойство IsBackground, определенное в классе Thread, следующим образом. public bool IsBackground { get; set; } Для того чтобы сделать поток фоновым, достаточно присвоить логическое значение true свойству IsBackground. А логическое значение false указывает на то, что поток является приоритетным. Приоритеты потоков У каждого потока имеется свой приоритет, который отчасти определяет, насколько часто поток получает доступ к ЦП. Вообще говоря, низкоприоритетные потоки получают доступ к ЦП реже, чем высокоприоритетные. Таким образом, в течение заданного промежутка времени низкоприоритетному потоку будет доступно меньше времени ЦП, чем высокоприоритетному. Как и следовало ожидать, время ЦП, получаемое потоком, оказывает определяющее влияние на характер его выполнения и взаимодействия с другими потоками, исполняемыми в настоящий момент в системе. Следует иметь в виду, что, помимо приоритета, на частоту доступа потока к ЦП оказывают влияние и другие факторы. Так, если высокоприоритетный поток ожидает доступа к некоторому ресурсу, например для ввода с клавиатуры, он блокируется, а вместо него выполняется низкоприоритетный поток. В подобной ситуации низкоприоритетный поток может получать доступ к ЦП чаще, чем высокоприоритетный поток в течение определенного периода времени. И наконец, конкретное планирование задач на уровне операционной системы также оказывает влияние на время ЦП, выделяемое для потока. Когда порожденный поток начинает выполняться, он получает приоритет, устанавливаемый по умолчанию. Приоритет потока можно изменить с помощью свойства Priority, являющегося членом класса Thread. Ниже приведена общая форма данного свойства: public ThreadPriority Priority{ get; set; } где ThreadPriority обозначает перечисление, в котором определяются приведенные ниже значения приоритетов. ThreadPriority.Highest ThreadPriority.AboveNormal ThreadPriority.Normal ThreadPriority.BelowNormal ThreadPriority.Lowest По умолчанию для потока устанавливается значение приоритета ThreadPriority. Normal. Для того чтобы стало понятнее влияние приоритетов на исполнение потоков, обратимся к примеру, в котором выполняются два потока: один с более высоким приоритетом. Оба потока создаются в качестве экземпляров объектов класса MyThread. В методе Run () организуется цикл, в котором подсчитывается определенное число повторений. Цикл завершается, когда подсчет достигает величины 1000000000 или когда статическая переменная stop получает логическое значение true. Первоначально переменная stop получает логическое значение false. В первом потоке, где производится подсчет до 1000000000, устанавливается логическое значение true переменной stop. В силу этого второй поток оканчивается на следующем своем интервале времени. На каждом шаге цикла строка в переменной currentName проверяется на наличие имени исполняемого потока. Если имена потоков не совпадают, это означает, что произошло переключение исполняемых задач. Всякий раз, когда происходит переключение задач, имя нового потока отображается и присваивается переменной currentName. Это дает возможность отследить частоту доступа потока к ЦП. По окончании обоих потоков отображается число повторений цикла в каждом из них. // Продемонстрировать влияние приоритетов потоков. using System; using System.Threading; class MyThread { public int Count; public Thread Thrd; static bool stop = false; static string currentName; /* Сконструировать новый поток. Обратите внимание на то, что данный конструктор еще не начинает выполнение потоков. */ public MyThread(string name) { Count = 0; Thrd = new Thread(this.Run); Thrd.Name = name; currentName = name; } // Начать выполнение нового потока, void Run() { Console.WriteLine("Поток " + Thrd.Name + " начат."); do { Count++; 10>10> |