Васильев А.Н. Основы программирования на C#. Васильев А. Н. Программирование
Скачать 5.54 Mb.
|
Результат выполнения программы (из листинга 6.6) Ƚɥɚɜɧɵɣ ɩɨɬɨɤ Base ɡɚɩɭɳɟɧ... ɇɚɱɚɥɶɧɨɟ ɡɧɚɱɟɧɢɟ: 0 ɉɨɬɨɤ Alpha ɡɚɩɭɳɟɧ... ɉɨɬɨɤ Bravo ɡɚɩɭɳɟɧ... ɂɬɨɝɨɜɨɟ ɡɧɚɱɟɧɢɟ: -1 Ƚɥɚɜɧɵɣ ɩɨɬɨɤ Base В главном классе объявляется статическое целочисленное поле state с начальным нулевым значением. Еще у насесть статический метод run() с аргументом логического типа (обозначен как type). В теле метода командой Thread t=Thread.CurrentThread объявляется Многопоточное программирование 301 объектная переменная t класса Thread, а в качестве значения переменной присваивается ссылка на объект потока, в котором выполняется метод. Ссылку на объект потока мы получаем с помощью статического свойства CurrentThread класса Thread. После этого появляется сообщение с названием потока, в котором выполняется метод. Название потока получаем с помощью инструкции t.Name. При этом мы приняли во внимание, что переменная t ссылается на объект потока. После отображения сообщения запускается оператор цикла while, в котором условием указано значение true. Поэтому формально получаем бесконечный цикл (оператор цикла, который не останавливается. Если метод run() вызван с аргументом true, то за каждую итерацию цикла значение поля state увеличивается на единицу. Если метод run() вызван с аргументом, то за каждую итерацию значение поля state уменьшается на единицу. После каждого изменения значения поля state выполняется пауза в 1 секунду (команда В главном методе программы командой Thread t=Thread. CurrentThread в переменную t записывается ссылка на главный поток программы НАЗ А МЕТКУ В методе run() была аналогичная команда. Но, во-первых, переменная в методе run() и переменная t в главном методе — это совершенно разные переменные. Во-вторых, значением инструкции является ссылка на объект потока, в котором эта инструкция выполняется. Поэтому в методе в переменную t записывается ссылка на объект потока, в котором выполняется метода в главном методе в переменную t записывается ссылка на объект главного потока. Командой t.Name= ƎBaseƎ главному потоку присваивается название. Отображается сообщение о запуске главного потока и начальное значение поля state. Дочерние потоки (объекты) создаются командами Thread up=new Thread(()=>run(true)) и Thread down=new Thread(()=>run(false)). В этих командах методы, выполняемые в потоках, определяются с помощью лямбда-выражений. В итоге в методе up будет выполняться метод run() с аргументом true (поток увеличивает значение поля state), а в потоке down будет выполняться метод run() с аргументом false (поток уменьшает значение поля state). Перед запуском для потоков задаются названия Глава команды up.Name= ƎAlphaƎ и down.Name=ƎBravoƎ) и приоритет (команды up.Priority=ThreadPriority.Highest и down. Priority=ThreadPriority.Lowest ). Для запуска потоков на выполнение используем команды up.Start() и down.Start(). { i НАЗ А МЕТКУ Откровенно говоря, в данном случае приоритет потоков мало на что влияет. Команды, определяющие приоритет потоков, приведены исключительно как иллюстрация к способу использования соответствующих свойств объекта потока, не более. Мы останавливаем выполнение дочерних потоков из главного потока. Командой Thread.Sleep(5000) выполняется приостановка выполнения главного потока на 5 секунд, после чего командами up.Abort() и down.Abort() завершается выполнение дочерних потоков НАЗ А МЕТКУ Напомним, что метод run() содержит бесконечный цикла созданные потоки не являются фоновыми, поэтому они должны были бы выполняться бесконечно долго. После завершения работы дочерних потоков проверяется значение поля state . От запуска к запуску это значение может меняться, но обычно оно несильно отличается от начального значения 0 (поскольку каждый из дочерних потоков успевает выполнить примерно одинаковое количество циклов по изменению значения поля. Завершается выполнение главного потока отображением сообщения соответствующего содержания. Синхронизация потоков Ни минуты покоя. Даже в воскресенье немо- гут вернуться не все сразу. из к/ф Гостья из будущего» Обычно разные потоки используют общие ресурсы. Скажем, в предыдущем примере два потока одновременно пытались изменить значение одного итого же статического поля. Чтобы понять, какие потенциальные Многопоточное программирование 303 проблемы могут возникнуть в подобных ситуациях, рассмотрим пример, не имеющий прямого отношения к программированию. Предположим, что имеется банковский счет, на котором есть определенная сумма денег. Доступ к этому счету имеет несколько человек, которые могут снимать деньги со счета и пополнять счет. Пускай для определенности на счету находится сумма в 1000 денежных единиц (неважно каких. Два клиента пытаются одновременно выполнить такие операции первый клиент пытается внести насчет сумму в 100 денежных единиц второй клиент пытается снять со счета сумму в 100 денежных еди- ниц. Итог операции должен быть нулевой сколько денег снято, столько же и поступает насчет. Но теперь рассмотрим технологию процесса. Деньги снимаются и зачисляются через удаленные терминалы. Операция сводится к приему и выдаче наличных и изменению записи о состоянии счета. Привнесении наличных насчет сначала выполняется запрос о получении текущего состояния счета, затем об увеличении этого значения на 100 (сумма, перечисляемая насчет) и, наконец, о выполнении записи о новом состоянии счета. При снятии наличных также считывается текущая сумма на счете, эта сумма уменьшается на 100 (то, что снимает со счета клиент, и новое значение записывается как состояние счета. Например, если сначала выполняется операция по зачислению средств, то будет прочитано значение 1000 (начальное состояние счета, вычислена сумма 1100 и это значение записано в качестве новой суммы средств на счете. Далее со счета снимаются наличные считывается сумма 1100 на счете, вычисляется новое значение 1000, и оно записывается как текущее состояние счета. Если поменять порядок выполнения операций (сначала снять деньги, а потом внести такую же сумму насчет, конечный результат не изменится. Но давайте представим, что оба процесса (снятие наличных и перечисление наличных насчет) выполняются практически одновременно. Скажем, при зачислении денег насчет считывается сумма 1000 и до того, как будет записано новое состояние счета, выполняется снятие денег со счета. Второй процесс также считает текущую сумму 1000. Первый процесс вычислит новое значение 1100, а второй процесс вычислит новое значение 900. Дальше все зависит оттого, какой процесс быстрее сделает запись о новом состоянии счета. Если первый процесс окажется быстрее, то сначала будет записано значение 1100, а затем второй процесс исправит это значение на 900. Глава Если задачу быстрее решит второй процесс, то итоговая сумма окажется равной В чем тут проблема Проблема в том, что один процесс вмешался в работу другого процесса. Какой выход из ситуации Можно заблокировать ресурс (в данном случае это счет пока с ресурсом работает один процесс, другим процессам доступ к ресурсу запрещен. Как только первый процесс завершил работу с ресурсом, с ним могут работать другие процессы (нов том же режиме — пока процесс работает с ресурсом, другие процессы к ресурсу доступа не имеют). Похожая ситуация может возникнуть в программировании при одновременном обращении нескольких потоков к одному общему ресурсу (например, объекту. Если мы хотим избежать проблем из-за одновременного доступа нескольких потоков к общему объекту, этот объект можно заблокировать на время выполнения потока. Для этого используют инструкцию lock. В круглых скобках после инструкции указывается блокируемый объекта в фигурных скобках — блок команд, при выполнении которых объект блокируется. Общий шаблон использования инструкции в этом случае следующий // Пример, в котором используется блокировка объекта вовремя доступа к нему потоков, представлен в листинге Листинг 6.7. Синхронизация потоков System; using System.Threading; // Ʉɥɚɫɫ ɞɥɹ ɫɨɡɞɚɧɢɹ ɨɛɴɟɤɬɚ: class MyClass{ // Ɉɬɤɪɵɬɨɟ ɰɟɥɨɱɢɫɥɟɧɧɨɟ ɩɨɥɟ: public int state=0; } // Ʉɥɚɫɫ ɫ ɝɥɚɜɧɵɦ ɦɟɬɨɞɨɦ: class LockDemo{ Многопоточное программирование // Ɇɟɬɨɞ ɞɥɹ ɜɵɩɨɥɧɟɧɢɹ ɜ ɩɨɬɨɤɟ: static void run(bool type,MyClass obj){ // ɐɟɥɨɱɢɫɥɟɧɧɵɟ ɩɟɪɟɦɟɧɧɵɟ: int val,k=1; // Ɉɛɴɟɤɬ ɞɥɹ ɝɟɧɟɪɢɪɨɜɚɧɢɹ ɫɥɭɱɚɣɧɵɯ ɱɢɫɟɥ: Random rnd=new Random(); // Ɉɩɟɪɚɬɨɪ ɰɢɤɥɚ: while(k<=5){ // Ȼɥɨɤɢɪɨɜɤɚ ɨɛɴɟɤɬɚ: lock(obj){ // ɋɱɢɬɵɜɚɧɢɟ ɡɧɚɱɟɧɢɹ ɩɨɥɹ: val=obj.state; // Ɉɬɨɛɪɚɠɟɧɢɟ ɩɪɨɱɢɬɚɧɧɨɝɨ ɡɧɚɱɟɧɢɹ: Console.WriteLine( Ǝ{0,4}: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ {1,2}Ǝ, Thread.CurrentThread.Name,val); // ɋɥɭɱɚɣɧɚɹ ɡɚɞɟɪɠɤɚ ɜ ɜɵɩɨɥɧɟɧɢɢ ɩɨɬɨɤɚ: Thread.Sleep(rnd.Next(1000,3001)); // ɇɨɜɨɟ ɡɧɚɱɟɧɢɟ ɩɟɪɟɦɟɧɧɨɣ: if(type) val++; else val--; // ɉɨɥɸ ɨɛɴɟɤɬɚ ɩɪɢɫɜɚɢɜɚɟɬɫɹ ɧɨɜɨɟ ɡɧɚɱɟɧɢɟ: obj.state=val; // Ɉɬɨɛɪɚɠɟɧɢɟ ɧɨɜɨɝɨ ɡɧɚɱɟɧɢɹ ɩɨɥɹ: Console.WriteLine( Ǝ{0,4}: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ {1,2}Ǝ, Thread.CurrentThread.Name,obj.state); // ɇɨɜɨɟ ɡɧɚɱɟɧɢɟ ɢɧɞɟɤɫɧɨɣ ɩɟɪɟɦɟɧɧɨɣ: k++; } } } // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ Глава 6 306 // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ: MyClass obj=new MyClass(); // Ɉɛɴɟɤɬ ɩɟɪɜɨɝɨ ɩɨɬɨɤɚ: Thread up=new Thread(()=>run(true,obj)); // ɇɚɡɜɚɧɢɟ ɩɟɪɜɨɝɨ ɩɨɬɨɤɚ: up.Name= ƎUPƎ; // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ ɜɬɨɪɨɝɨ ɩɨɬɨɤɚ: Thread down=new Thread(()=>run(false,obj)); // ɇɚɡɜɚɧɢɟ ɜɬɨɪɨɝɨ ɩɨɬɨɤɚ: down.Name= ƎDOWNƎ; // Ɂɚɩɭɫɤ ɩɟɪɜɨɝɨ ɩɨɬɨɤɚ ɧɚ ɜɵɩɨɥɧɟɧɢɟ: up.Start(); // Ɂɚɩɭɫɤ ɜɬɨɪɨɝɨ ɩɨɬɨɤɚ ɧɚ ɜɵɩɨɥɧɟɧɢɟ: down.Start(); Результат выполнения программы представлен ниже: Результат выполнения программы (из листинга 6.7) UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 Многопоточное программирование ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ В программе описан классу которого есть целочисленное открытое поле state (с начальным нулевым значением НАЗ А МЕТКУ bНестатическое поле state инициализировано начальным нулевым значением непосредственно в инструкции объявления поля в классе. Это означает, что у всех объектов класса MyClass при создании поле state будет получать начальное нулевое значение. Класс MyClass нам нужен для того, чтобы на его основе создать объект, за который будут конкурировать дочерние потоки. Дочерние потоки создаются на основе статического метода run(), описанного в главном классе (точнее, потоки создаются на основе анонимных методов, в которых вызывается метод run()). У метода два аргумента аргумент type логического типа и аргумент obj, который является ссылкой на объект класса MyClass. При выполнении метода запускается оператор цикла while , в котором командой val=obj.state в переменную val записывается текущее значение поля state объекта obj (объект, переданный методу в качестве аргумента. Это значение отображается в консольном окне. Затем выполняется задержка в выполнении потока. Время задержки является случайной величиной в диапазоне от 1 до 3 секунд. Далее, в зависимости от значения первого логического аргумента метода, значение переменной val увеличивается или уменьшается на единицу, это новое значение присваивается полю state объекта obj (команда obj. state=val ), после чего новое значение поля отображается в консольном окне Глава 6 308 q ПОДРОБНОСТИ bДля генерирования случайных чисел командой Random rnd=new Random() создается объект rnd класса Random . Результатом выражения является целое случайное число в диапазоне возможных значений от 1000 до Инструкции {0,4} ив строке форматирования в методе WriteLine() означают, что для отображения первого (после строки форматирования) аргумента выделяется не менее 4 позиций, а для отображения второго аргумента выделяется не менее 2 позиций. Значением выражения Thread.CurrentThread.Name является имя потока, в котором вычисляется это выражение. Здесь уместно напомнить, что статическое свойство CurrentThread класса Thread результатом возвращает ссылку на объект потока, в котором вычисляется значение свойства. Но самое важное — это то, что все описанные выше действия выполняются внутри блока на основе инструкции блок начинается с выражения, означающего, что на время выполнения команд в следующем после этого выражения блоке объект obj блокируется для доступа из других потоков. В главном методе программы создается объект obj класса MyClass. Также создаются объекты up и down класса Thread. В обоих случаях объекты потоков создаются на основе лямбда-выражений. В потоке up выполняется метод run() с аргументами true и obj. В потоке down выполняется метод run() с аргументами false и obj. Поэтому при выполнении потока up выполняются операции по увеличению значения поля state объекта obj. Напротив, поток down выполняет операции по уменьшению значения поля state объекта Командами up.Name= ƎUPƎ и down.Name=ƎDOWNƎ задаются названия для потоков, а командами up.Start() и down.Start() потоки запускаются на выполнение. Что можно сказать о результате выполнения программы Сообщения от потоков выводятся парами, а конечное значение поля state объекта совпадает с начальным нулевым значением этого поля. Это последствия блокирования объекта obj при работе с ним потоков. Для сравнения интересно посмотреть, каким может быть результат выполнения программы, если не использовать блокировку объекта obj. Многопоточное программирование 309 Для этого в программном коде (см. Листинг 6.7) следует закомменти- ровать инструкцию lock(obj) (с открывающей фигурной скобкой) и закрывающую фигурную скобку блока в теле метода run(). Соответствующие места в программном коде выделены жирным шрифтом. Возможный результат выполнения программы в этом случае может выглядеть так, как показано ниже: Результат выполнения программы (из листинга 6.7) UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ -1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ -1 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ -2 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 2 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 2 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 2 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 3 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 3 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 1 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 1 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 4 UP: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 4 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 0 DOWN: ɩɪɨɱɢɬɚɧɨ ɡɧɚɱɟɧɢɟ 0 UP: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ 5 DOWN: ɩɪɢɫɜɨɟɧɨ ɡɧɚɱɟɧɢɟ -Общий вывод состоит в том, что условие равенства начального и конечного значений поля state объекта obj не выполнено. И это является следствием нескоординированной работы потоков с общим ресурсом, которым в данном случае является объект obj. Глава Использование потоков Как это вы успеваете, Филипп Филиппович Успевает всюду тот, кто никуда не торопится. из к/ф Собачье сердце» В этом разделе мы рассмотрим несколько примеров, в которых используются потоки. В примере, представленном в листинге 6.8, два дочерних потока заполняют символьный массив. Один из потоков заполняет массив с конца до начала, присваивая элементам массива кириллические буквы. Второй поток заполняет массив сначала и до конца, присваивая элементам массива латинские буквы. Потоки работают, пока не встретятся где-то посредине массива. Рассмотрим представленный далее программный код. Листинг 6.8. Заполнение массива потоками System; using System.Threading; // Ƚɥɚɜɧɵɣ ɤɥɚɫɫ: class FillingArrayDemo{ // Ƚɥɚɜɧɵɣ ɦɟɬɨɞ: static void Main(){ // Ɋɚɡɦɟɪ ɦɚɫɫɢɜɚ: int size=20; // ɋɨɡɞɚɧɢɟ ɦɚɫɫɢɜɚ: char[] symbs=new char[size]; // Ɂɚɩɨɥɧɟɧɢɟ ɦɚɫɫɢɜɚ ƎɡɜɟɡɞɨɱɤɚɦɢƎ: for(int k=0;k ƍ*ƍ; Console.Write( Ǝ|Ǝ+symbs[k]); } Console.WriteLine( Ǝ|Ǝ); // ɂɧɞɟɤɫ ɩɟɪɜɨɝɨ ɢ ɩɨɫɥɟɞɧɟɝɨ ɷɥɟɦɟɧɬɚ ɜ ɦɚɫɫɢɜɟ: int first=0,second=symbs.Length-1; // Ɉɛɴɟɤɬɧɵɟ ɩɟɪɟɦɟɧɧɵɟ ɞɥɹ ɩɨɬɨɤɨɜ: Многопоточное программирование Thread A,B; // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ ɞɥɹ ɩɟɪɜɨɝɨ ɩɨɬɨɤɚ: A=new Thread(()=>{ // ɇɚɱɚɥɶɧɵɣ ɫɢɦɜɨɥ ɞɥɹ ɡɚɩɨɥɧɟɧɢɹ ɦɚɫɫɢɜɚ: char start=' ə'; // Ȼɟɫɤɨɧɟɱɧɵɣ ɰɢɤɥ: while(true){ // ȿɫɥɢ ɜɬɨɪɨɣ ɢɧɞɟɤɫ ɛɨɥɶɲɟ ɩɟɪɜɨɝɨ ɢɧɞɟɤɫɚ: if(second>first){ // Ɂɧɚɱɟɧɢɟ ɷɥɟɦɟɧɬɚ: symbs[second]=start; // ɇɨɜɵɣ ɫɢɦɜɨɥ ɞɥɹ ɩɪɢɫɜɚɢɜɚɧɢɹ: start--; // ɇɨɜɨɟ ɡɧɚɱɟɧɢɟ ɢɧɞɟɤɫɚ: second--; // Ɂɚɞɟɪɠɤɚ ɜ ɜɵɩɨɥɧɟɧɢɢ ɩɨɬɨɤɚ: Thread.Sleep(100); } // ȿɫɥɢ ɜɬɨɪɨɣ ɢɧɞɟɤɫ ɧɟ ɛɨɥɶɲɟ ɩɟɪɜɨɝɨ: else{ // Ɂɚɜɟɪɲɟɧɢɟ ɜ ɜɵɩɨɥɧɟɧɢɢ ɩɨɬɨɤɚ: Thread.CurrentThread.Abort(); } } }); // ɋɨɡɞɚɧɢɟ ɨɛɴɟɤɬɚ ɞɥɹ ɜɬɨɪɨɝɨ ɩɨɬɨɤɚ: B=new Thread(()=>{ // ɇɚɱɚɥɶɧɵɣ ɫɢɦɜɨɥ ɞɥɹ ɡɚɩɨɥɧɟɧɢɹ ɦɚɫɫɢɜɚ: char start='A'; // Ȼɟɫɤɨɧɟɱɧɵɣ ɰɢɤɥ: while(true){ // ȿɫɫɥɢ ɩɟɪɜɵɣ ɢɧɞɟɤɫ ɦɟɧɶɲɟ ɜɬɨɪɨɝɨ: Глава 6 312 if(first Ɂɧɚɱɟɧɢɟ ɷɥɟɦɟɧɬɚ: symbs[first]=start; // ɇɨɜɵɣ ɫɢɦɜɨɥ ɞɥɹ ɩɪɢɫɜɚɢɜɚɧɢɹ: start++; // ɇɨɜɨɟ ɡɧɚɱɟɧɢɟ ɢɧɞɟɤɫɚ: first++; // Ɂɚɞɟɪɠɤɚ ɜ ɜɵɩɨɥɧɟɧɢɢ ɩɨɬɨɤɚ: Thread.Sleep(100); } // ȿɫɥɢ ɩɟɪɜɵɣ ɢɧɞɟɤɫ ɧɟ ɦɟɧɶɲɟ ɜɬɨɪɨɝɨ: else{ // Ɂɚɜɟɪɲɟɧɢɟ ɜɵɩɨɥɧɟɧɢɹ ɩɨɬɨɤɚ: Thread.CurrentThread.Abort(); } } }); // Ɂɚɩɭɫɤ ɩɟɪɜɨɝɨ ɩɨɬɨɤɚ ɧɚ ɜɵɩɨɥɧɟɧɢɟ: A. Start(); // Ɂɚɩɭɫɤ ɜɬɨɪɨɝɨ ɩɨɬɨɤɚ ɧɚ ɜɵɩɨɥɧɟɧɢɟ: B. Start(); // Ɉɠɢɞɚɧɢɟ ɜɵɩɨɥɧɟɧɢɹ ɩɟɪɜɨɝɨ ɩɨɬɨɤɚ: if(A. IsAlive) A. Join(); // Ɉɠɢɞɚɧɢɟ ɜɵɩɨɥɧɟɧɢɹ ɜɬɨɪɨɝɨ ɩɨɬɨɤɚ: if(B. IsAlive) B. Join(); // Ɉɬɨɛɪɚɠɟɧɢɟ ɫɨɞɟɪɠɢɦɨɝɨ ɦɚɫɫɢɜɚ: for(int k=0;k Ǝ|Ǝ+symbs[k]); } Console.WriteLine( Ǝ|Ǝ); } } Многопоточное программирование |