Выбрать главу
ся применение данной формы метода Abort(). // Использовать форму метода Abort (object stateInfo). using System; using System.Threading; class MyThread { public Thread Thrd; public MyThread(string name) { Thrd = new Thread(this.Run); Thrd.Name = name; Thrd.Start(); } // Это точка входа в поток. void Run() { try { Console.WriteLine(Thrd.Name + " начат."); for (int i = 1; i <= 1000; i++) { Console.Write(i + " "); if((i%10)==0) { Console.WriteLine(); Thread.Sleep(250); } } Console.WriteLine(Thrd.Name + " завершен нормально."); } catch(ThreadAbortException exc) { Console.WriteLine("Поток прерван, код завершения " + exc.ExceptionState); } } } class UseAltAbort { static void Main() { MyThread mt1 = new MyThread("Мой Поток"); Thread.Sleep(1000); // разрешить порожденному потоку начать свое выполнение Console.WriteLine("Прерывание потока."); mt1.Thrd.Abort (100); mt1.Thrd.Join(); // ожидать прерывания потока Console.WriteLine("Основной поток прерван."); } } Эта программа дает следующий результат. Мой Поток начат 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Прерывание потока. Поток прерван, код завершения 100 Основной поток прерван. Как следует из приведенного выше результата, значение 100 передается методу Abort() в качестве кода прерывания. Это значение становится затем доступным по средством свойства ExceptionState из класса исключения ThreadAbortException, которое перехватывается потоком при его прерывании. Отмена действия метода Abort() Запрос на преждевременное прерывание может быть переопределен в са мом потоке. Для этого необходимо сначала перехватить в потоке исключение ThreadAbortException, а затем вызвать метод ResetAbort(). Благодаря этому исключается повторное генерирование исключения по завершении обработчика ис ключения, прерывающего данный поток. Ниже приведена форма объявления метода ResetAbort(). public static void ResetAbort() Вызов метода ResetAbort() может завершиться неудачно, если в потоке отсутству ет надлежащий режим надежной отмены преждевременного прерывания потока. В приведенном ниже примере программы демонстрируется применение метода ResetAbort(). // Использовать метод ResetAbort(). using System; using System.Threading; class MyThread { public Thread Thrd; public MyThread(string name) { Thrd = new Thread(this.Run); Thrd.Name = name; Thrd.Start(); } // Это точка входа в поток. void Run() { Console.WriteLine(Thrd.Name + ".начат."); for(int i = 1; i <= 1000; i++) { try { Console.Write(i + " "); if((i%10)==0) { Console.WriteLine(); Thread.Sleep(250); } } catch(ThreadAbortException exc) { if((int)exc.ExceptionState == 0) { Console.WriteLine("Прерывание потока отменено! " + "Код завершения " + exc.ExceptionState); Thread.ResetAbort(); } else Console.WriteLine("Поток прерван, код завершения " + exc.ExceptionState); } } Console.WriteLine(Thrd.Name + " завершен нормально."); } } class ResetAbort { static void Main() { MyThread mt1 = new MyThread("Мой Поток"); Thread.Sleep(1000); // разрешить порожденному потоку начать свое выполнение Console.WriteLine("Прерывание потока."); mt1.Thrd.Abort(0); // это не остановит поток Thread.Sleep(1000); // разрешить порожденному потоку выполняться подольше Console.WriteLine("Прерывание потока."); mt1.Thrd.Abort(100); // а это остановит поток mt1.Thrd.Join(); // ожидать прерывания потока Console.WriteLine("Основной поток прерван."); } } Ниже приведен результат выполнения этой программы. Мой Поток начат 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Прерывание потока. Прерывание потока отменено! Код завершения 0 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 Поток прерван, код завершения 100 Основной поток прерван. Если в данном примере программы метод Abort() вызывается с нулевым аргумен том, то запрос на преждевременное прерывание отменяется потоком, вызывающим метод ResetAbort(), и выполнение этого потока продолжается. Любое другое значе ние аргумента приведет к прерыванию потока. Приостановка и возобновление потока В первоначальных версиях среды .NET Framework поток можно было приостано вить вызовом метода Thread.Suspend() и возобновить вызовом метода Thread. Resume(). Но теперь оба эти метода считаются устаревшими и не рекомендуются к применению в новом коде. Объясняется это, в частности, тем, что пользоваться мето дом Suspend() на самом деле небезопасно, так как с его помощью можно приостано вить поток, который в настоящий момент удерживает блокировку, что препятствует ее снятию, а следовательно, приводит к взаимоблокировке. Применение обоих мето дов может стать причиной серьезных осложнений на уровне системы. Поэтому для приостановки и возобновления потока следует использовать другие средства синхро низации, в том числе мьютекс и семафор. Определение состояния потока Состояние потока может быть получено из свойства ThreadState, доступного в классе Thread. Ниже приведена общая форма этого свойства. public ThreadState ThreadState{ get; } Состояние потока возвращается в виде значения, определенного в перечислении ThreadState. Ниже приведены значения, определенные в этом перечислении. ThreadState.Aborted ThreadState.AbortRequested ThreadState.Background ThreadState.Running ThreadState.Stopped ThreadState.StopRequested ThreadState.Suspended ThreadState.SuspendRequested ThreadState.Unstarted ThreadState.WaitSleepJoin Все эти значения не требуют особых пояснений, за исключением одного. Значение ThreadState.WaitSleepJoin обозначает состояние, в которое поток переходит во время ожидания в связи с вызовом метода Wait(), Sleep() или Join(). Применение основного потока Как пояснялось в самом начале этой главы, у всякой программы на C# имеется хотя бы один поток исполнения, называемый основным. Этот поток программа получает автоматически, как только начинает выполняться. С основным потоком можно обра щаться таким же образом, как и со всеми остальными потоками. Для доступа к основному потоку необходимо получить объект типа Thread, кото рый ссылается на него. Это делается с помощью свойства CurrentThread, являющего ся членом класса Thread. Ниже приведена общая форма этого свойства. public static Thread CurrentThread{ get; } Данное свойство возвращает ссылку на тот поток, в котором оно используется. По этому если свойство CurrentThread используется при выполнении кода в основном потоке, то с его помощью можно получить ссылку на основной поток. Имея в своем распоряжении такую ссылку, можно управлять основным потоком так же, как и лю бым другим потоком. В приведенном ниже примере программы сначала получается ссылка на основной поток, а затем получаются и устанавливаются имя и приоритет основного потока. // Продемонстрировать управление основным потоком. using System; using System.Threading; class UseMain { static void Main() { Thread Thrd; // Получить основной поток. Thrd = Thread.CurrentThread; // Отобразить имя основного потока. if(Thrd.Name == null) Console.WriteLine("У основного потока нет имени."); else Console.WriteLine("Основной поток называется: " + Thrd.Name); // Отобразить приоритет основного потока. Console.WriteLine("Приоритет: " + Thrd.Priority); Console.WriteLine(); // Установить имя и приоритет. Console.WriteLine("Установка имени и приоритета.\n"); Thrd.Name = "Основной Поток"; Thrd.Priority = ThreadPriority.AboveNormal; Console.WriteLine("Теперь основной поток называется: " + Thrd.Name); Console.WriteLine("Теперь приоритет: " + Thrd.Priority); } } Ниже приведен результат выполнения этой программы. У основного потока нет имени. Приоритет: Normal Установка имени и приоритета. Теперь основной поток называется: Основной Поток Теперь приоритет: AboveNormal Следует, однако, быть очень внимательным, выполняя операции с основным пото ком. Так, если добавить в конце метода Main() следующий вызов метода Join(): Thrd.Join(); программа никогда не завершится, поскольку она будет ожидать окончания основного потока! Дополнительные средства многопоточной обработки, внедренные в версии .NET Framework 4.0 В версии .NET Framework 4.0 внедрен ряд новых средств многопоточной обработки, которые могут оказаться весьма полезными. Самым важным среди них является новая система отмены. В этой системе поддерживается механизм отмены потока простым, вполне определенным и структурированным способом. В основу этого механизма по ложено понятие признака отмены, с помощью которого указывается состояние отмены потока. Признаки отмены поддерживаются в классе CancellationTokenSource и в структуре CancellationToken. Система отмены полностью интегрирована в новую библиотеку распараллеливания задач (TPL), и поэтому она подробнее рассматривает ся вместе с TPL в главе 24. В класс System.Threading добавлена структура SpinWait, предоставляющая ме тоды SpinOnce() и SpinUntil(), которые обеспечивают более полный контроль над ожиданием в состоянии занятости. Вообще говоря, структура SpinWait оказывается непригодной для однопроцессорных систем. А для многопроцессорных систем она применяется в цикле. Еще одним элементом, связанным с ожиданием в состоянии за нятости, является структура SpinLock, которая применяется в цикле ожидания до тех пор, пока не станет доступной блокировка. В класс Thread добавлен метод Yield(), который просто выдает остаток кванта времени, выделенного потоку. Ниже приведена общая форма объявления этого метода. public static bool Yield() Этот метод возвращает логическое значение true, если происходит переключение контекста. В отсутствие другого потока, готового для выполнения, переключение кон текста не произойдет. Рекомендации по многопоточному программированию Для эффективного многопоточного программирования самое главное — мыслить категориями параллельного, а не последовательного выполнения кода. Так, если в одной программе имеются две подсистемы, которые могут работать параллельно, их следует организовать в отдельные потоки. Но делать это следует очень внимательно и аккуратно, поскольку если создать слишком много потоков, то тем самым можно значительно снизить, а не повысить производительность программы. Следует также иметь в виду дополнительные издержки, связанные с переключением контекста. Так, если создать слишком много потоков, то на смену контекста уйдет больше времени ЦП, чем на выполнение самой программы! И наконец, для написания нового кода, предназначенного для многопоточной обработки, рекомендуется пользоваться библи отекой распараллеливания задач (TPL), о которой речь пойдет в следующей главе. Запуск отдельной задачи Многозадачность на основе потоков чаще всего организуется при программирова нии на С#. Но там, где это уместно, можно организовать и многозадачность на основе процессов. В этом случае вместо запуска другого потока в одной и той же программе одна программа начинает выполнение другой. При программировании на C# это делается с помощью класса Process, определенного в пространстве имен System. Diagnostics. В заключение этой главы вкратце будут рассмотрены особенности за пуска и управления другим процессом. Простейший способ запустить другой процесс — воспользоваться методом Start(), определенным в классе Process. Ниже приведена одна из самых простых форм этого метода: public static Process Start(string имя
файла) где имя_файла обозначает конкретное имя файла, который должен исполняться или же связан с исполняемым файлом. Когда созданный процесс завершается, следует вызвать метод Close(), чтобы осво бодить память, выделенную для этого процесса. Ниже приведена форма объявления метода Close(). public void Close() Процесс может быть прерван двумя способами. Если процесс является приложе нием Windows с графическим пользовательским интерфейсом, то для прерывания такого процесса вызывается метод CloseMainWindow(), форма которого приведена ниже. public bool CloseMainWindow() Этот метод посылает процессу сообщение, предписывающее ему остановиться. Он возвращает логическое значение true, если сообщение получено, и логическое значе ние false, если приложение не имеет графического пользовательского интерфейса или главного окна. Следует, однако, иметь в виду, что метод CloseMainWindow() слу жит только для запроса остановки процесса. Если приложение проигнорирует такой запрос, то оно не будет прервано как процесс. Для безусловного прерывания процесса следует вызвать метод Kill(), как пока зано ниже. public void Kill() Но методом Kill() следует пользоваться аккуратно, так как он приводит к некон тролируемому прерыванию процесса. Любые несохраненные данные, связанные с пре рываемым процессом, будут, скорее всего, потеряны. Для того чтобы организовать ожидание завершения процесса, можно воспользо ваться методом WaitForExit(). Ниже приведены две его формы. public void WaitForExit() public bool WaitForExit(int миллисекунд) В первой форме ожидание продолжается до тех пор, пока процесс не завершится, а во второй форме — только в течение указанного количества миллисекунд. В послед нем случае метод WaitForExit() возвращает логическое значение true, если процесс завершился, и логическое значение false, если он все еще выполняется. В приведенном ниже примере программы демонстрируется создание, ожидание и закрытие процесса. В этой программе сначала запускается стандартная сервисная про грамма Windows: текстовый редактор WordPad.exe, а затем организуется ожидание завершения программы WordPad как процесса. // Продемонстрировать запуск нового процесса. using System; using System.Diagnostics; class StartProcess { static void Main() { Process newProc = Process.Start("wordpad.exe"); Console.WriteLine("Новый процесс запущен."); newProc.WaitForExit(); newProc.Close(); // освободить выделенные ресурсы Console.WriteLine("Новый процесс завершен."); } } При выполнении этой программы запускается стандартное приложение WordPad, и на экране появляется сообщение "Новый процесс запущен.". Затем программа ожидает закрытия WordPad. По окончании работы WordPad на экране появляется за ключительное сообщение "Новый процесс завершен.".