События синхронизации потоков

93

События представляют собой еще один ресурс для обеспечения синхронизации в масштабе всей системы.

Для использования системных событий из управляемого кода .NET Framework предлагает классы ManualResetEvent, AutoResetEvent, ManualResetEventSlim и CountdownEvent, которые находятся в пространстве имен System.Threading. Классы ManualResetEventSlim и CountdownEvent появились в версии .NET 4.

Эти классы являются производными от класса EventWaitHandle, находящегося на верхнем уровне иерархии классов, и применяются в тех случаях, когда один поток ожидает появления некоторого события в другом потоке. Как только такое событие появляется, второй поток уведомляет о нем первый поток, позволяя тем самым возобновить его выполнение.

Ниже приведены конструкторы классов ManualResetEvent и AutoResetEvent:

public ManualResetEvent(bool initialState)
public AutoResetEvent(bool initialState)

Если в обеих формах параметр initialState имеет логическое значение true, то о событии первоначально уведомляется. А если он имеет логическое значение false, то о событии первоначально не уведомляется.

Применяются события очень просто. Так, для события типа ManualResetEvent порядок применения следующий. Поток, ожидающий некоторое событие, вызывает метод WaitOne() для событийного объекта, представляющего данное событие. Если событийный объект находится в сигнальном состоянии, то происходит немедленный возврат из метода WaitOne(). В противном случае выполнение вызывающего потока приостанавливается до тех пор, пока не будет получено уведомление о событии. Как только событие произойдет в другом потоке, этот поток установит событийный объект в сигнальное состояние, вызвав метод Set(). Поэтому метод Set() следует рассматривать как уведомляющий о том, что событие произошло.

После установки событийного объекта в сигнальное состояние произойдет немедленный возврат из метода WaitOne(), и первый поток возобновит свое выполнение. А в результате вызова метода Reset() событийный объект возвращается в несигнальное состояние.

Событие AutoResetEvent отличается от события типа ManualResetEvent лишь способом установки в исходное состояние. Если для события типа ManualResetEvent событийный объект остается в сигнальном состоянии до тех пор, пока не будет вызван метод Reset(), то для события типа AutoResetEvent событийный объект автоматически переходит в несигнальное состояние, как только поток, ожидающий это событие, получит уведомление о нем и возобновит свое выполнение. Поэтому если применяется событие типа AutoResetEvent, то вызывать метод Reset() необязательно.

Событие ManualResetEventSlim переводится в сигнальное состояние вызовом метода Set(), а с помощью Reset() возвращается обратно в несигнальное состояние. В случае вызова метода Set() при наличии множества потоков, ждущих перехода события в сигнальное состояние, ожидание всех этих потоков немедленно прекращается. В случае, если поток просто вызывает метод WaitOne(), а событие уже находится в сигнальном состоянии, ожидавший поток может сразу же продолжить работу.

В приведенном ниже примере программы демонстрируется применение события типа ManualResetEvent:

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class MyThread
    {
        public Thread Thrd;
        ManualResetEvent mre;

        public MyThread(string name, ManualResetEvent evt)
        {
            Thrd = new Thread(this.Run);
            Thrd.Name = name;
            mre = evt;
            Thrd.Start();
        }

        void Run()
        {
            Console.WriteLine("Внутри потока " + Thrd.Name);

            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(Thrd.Name);
                Thread.Sleep(500);
            }

            Console.WriteLine(Thrd.Name + " завершен!");

            // Уведомление о событии
            mre.Set();
        }
    }

    class Program
    {
        static void Main()
        {
            ManualResetEvent evtObj = new ManualResetEvent(false);

            MyThread mt1 = new MyThread("Событийный поток 1",evtObj);

            Console.WriteLine("Основной поток ожидает событие");

            evtObj.WaitOne();

            Console.WriteLine("Основной поток получил уведомление о событии от первого потока");

            evtObj.Reset();

            mt1 = new MyThread("Событийный поток 2",evtObj);

            evtObj.WaitOne();

            Console.WriteLine("Основной поток получил уведомление о событии от второго потока");
            Console.ReadLine();
        }
    }
}
Использование событий синхронизации

Прежде всего обратите внимание на то, что событие типа ManualResetEvent передается непосредственно конструктору класса MyThread. Когда завершается метод Run() из класса MyThread, он вызывает для событийного объекта метод Set(), устанавливающий этот объект в сигнальное состояние. В методе Main() формируется событийный объект evtObj типа ManualResetEvent, первоначально устанавливаемый в исходное, несигнальное состояние. Затем создается экземпляр объекта типа MyThread, которому передается событийный объект evtObj.

После этого основной поток ожидает уведомления о событии. А поскольку событийный объект evtObj первоначально находится в несигнальном состоянии, то основной поток вынужден ожидать до тех пор, пока для экземпляра объекта типа MyThread не будет вызван метод Set(), устанавливающий событийный объект evtObj в сигнальное состояние. Это дает возможность основному потоку возобновить свое выполнение.

Затем событийный объект устанавливается в исходное состояние, и весь процесс повторяется, но на этот раз для второго потока. Если бы не событийный объект, то все потоки выполнялись бы одновременно, а результаты их выполнения оказались бы окончательно запутанными. Для того чтобы убедиться в этом, попробуйте закомментировать вызов метода WaitOne() в методе Main().

Если бы в рассматриваемой здесь программе событийный объект типа AutoResetEvent использовался вместо событийного объекта типа ManualResetEvent, то вызывать метод Reset() в методе Main() не пришлось бы. Ведь в этом случае событийный объект автоматически устанавливается в несигнальное состояние, когда поток, ожидающий данное событие, возобновляет свое выполнение. Для опробования этой разновидности события замените в данной программе все ссылки на объект типа ManualResetEvent ссылками на объект типа AutoResetEvent и удалите все вызовы метода Reset(). Видоизмененная версия программы будет работать так же, как и прежде.

Пройди тесты
Лучший чат для C# программистов