Добавить в цитаты Настройки чтения

Страница 225 из 372

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

В связи с выполнением условия _sum == 5 выполняется вызов Monitor.Wait (this). В этот момент первый поток освобождает объект this и становится в очередь ожидания. Эта еще одна, связанная с объектом очередь (наряду с очередью потоков, готовых к выполнению). Разница между ними состоит в следующем. Очередной поток из очереди готовых к выполнению потоков начинает выполняться, если текущий исполняемый поток завершил выполнение критической секции (вызвал Monitor.Exit (this), то есть освободил объект this). Потоки из очереди ожидания становятся в очередь потоков готовых к выполнению, если текущий исполняемый поток вызвал Monitor.Pulse (this), сигнализируя тем самым, что состояние объекта this изменилось и ожидающие потоки могут работать с данным объектом.

Таким образом, первый поток стоит в очереди ожидания, а тем временем второй (и, возможно, другие потоки) пополняет счет. Как только счет достигнет суммы в 505 условных единиц, первый поток попадает в очередь готовых к выполнению потоков и начинает работать.

Делегаты, регистрация callback делегата в пуле рабочих потоков

В коде метода InitIfNecessary класса SynchronizationAttribute используются упомянутые в заголовке данного раздела сущности. Познакомимся с их применением в процессе разбора следующего примера:

using System;

using System.Threading;

public class Test {

     private AutoResetEvent _myEvent;

     private int _count = 0;

     public Test() {

          Console.WriteLine("»> Test constructor thread = " +

                    Thread.CurrentThread.GetHashCode() +

                    " IsPoolThread = " +

                    Thread.CurrentThread.IsThreadPoolThread);

           _myEvent = new AutoResetEvent(false);

          WaitOrTimerCallback myCallBackDelegate =

               new WaitOrTimerCallback(this.MyCallBack);

          ThreadPool.RegisterWaitForSingleObject Х

               _myEvent,

               myCallBackDelegate,

               null,

               100,

                false);

     }

      public int count {

             get { return _count; }

              set { _count = value;}

      }

      private delegate String MyDelegate ();

      private void MyCallBack (Object state, bool timedOut) {

             Console.WriteLine("»> MyCallback thread = " +

                                Thread.CurrentThread.GetHashCode() +

                                " IsPoolThread = " +

                                Thread.CurrentThread.IsThreadPoolThread);

              MyDelegate hello = new MyDelegate(MyHello);

              count++;

              Console.WriteLine(hello() + " Count = " + count +





                               " timedOut = " + timedOut);

       }

       private String MyHello() {

              return "Test_" + count +": ";

       }

       public void NewEvent() {

              _myEvent.Set();

       }

}

public class MyApp {

      public static void Main () {

              Console.WriteLine("ЮЮ> MyApp thread = " +

                                Thread.CurrentThread.GetHashCode() +

                                " IsPoolThread = " +

                                Thread.CurrentThread.IsThreadPoolThread);

               Test test = new Test();

               test.NewEvent();

               Thread.Sleep(500);

               test.NewEvent();

               Thread.Sleep(1000);

      }

}

Опишем прежде всего семантику нашего приложения.

Метод Main выполняется в основном потоке приложения. Все приложение в целом завершается по завершении этого метода (после возвращения из вызова функции Thread.Sleep (1000)). Параллельно с выполнением основного потока несколько раз успевает выполниться метод MyCallBack класса Test. Заметим, что этот метод выполняется так называемыми рабочими потоками, извлекаемыми системой из пула рабочих потоков. Рабочий поток не может пережить основной поток и по завершении последнего завершается и текущий рабочий поток.

Данное консольное приложение после запуска прежде всего выводит на консоль информацию о потоке, выполняющем код функции Main. Это хеш потока и информация о том, является ли данный поток рабочим потоком. Очевидно, что в данном случае значение последнего параметра должно быть равно false так как текущий поток является основным потоком приложения.

Далее вызывается конструктор класса Test. Здесь также выводится информация о потоке — том потоке, который выполняет код конструктора. Этот тот же самый основной поток приложения.

Далее в конструкторе создается экземпляр myEvent события типа AutoResetEvent. События в .NET еще не обсуждались в данном курсе, но в данном случае используется событие специального типа, реализованное в системе. В связи с этим пока будет достаточно рассмотреть только это событие и только в контексте данного примера.

Для работы с пулом потоков мы регистрируем с помощью статического метода ThreadPool.RegisterWaitForSingleObject делегат myCallBackDelegate специального известного системе типа WaitOrTimerCallback. Делегат является некоторым аналогом указателя на функцию (в данном случае на MуCаllBаск), которая должна иметь сигнатуру, заданную при объявлении делегата. В случае делегата типа WaitOrTimerCallback возвращаемое значение должно отсутствовать (void), первый аргумент должен иметь тип System.Object и может использоваться для передачи вызываемой функции произвольных данных, второй параметр должен иметь тип bool и система через него передает значение true, если данный вызов произошел в связи с истечением времени ожидания (см. объяснение ниже).

Функция, ссылка на которую передана в делегат, будет вызываться и исполняться некоторым рабочим потоком из пула как только произойдет одно из двух событий:

• Событие _myEvent (зарегестрированное вместе с делегатом) будет установлено в состояние signaled

Для установки события типа AutoResetEvent в данное состояние достаточно вызвать его метод Set ().

• Время ожидания превысило пороговое значение (четвертый параметр в ThreadPool.RegisterWaitForSingleObject)

Отсчет времени идет от момента регистрации делегата или от момента последнего его вызова.

Заметим, что при создании myEvent вызывался конструктор AutoResetEvent (false). Задание параметра false привело к созданию события, не находящегося в состоянии signaled. При задании в этом конструкторе параметра true инициированное событие сразу же находится в состоянии signaled, и делегат вызывается сразу же после его регистрации.

Событие AutoResetEvent обладает еще одним важным свойством, отраженным в его названии — оно переходит в исходное состояние автоматически после очередного вызова делегата.

Третий параметр в ThreadPool.RegisterwaitForSingleObject задает ссылку на объект, содержащий данные передаваемые связанной с делегатом функции при вызове последнего (в данном случае ничего не передается).