AutoResetEvent\ManualResetEvent–synchronizacja między procesami

AutoResetEvent\ManualResetEvent może być używany do synchronizacji międzyprocesowej tak samo jak np. mutex. Posiada podobny zestaw metod do tworzenia obiektu z nazwą oraz późniejszego jego otwierania.

Aby móc go użyć do synchronizacji międzyprocesowej należy oczywiście nadać obiektowi nazwę – tak samo jak to jest z Mutex. W tym problem, że konstruktory ManualResetEvent czy AutoResetEvent nie przyjmują takich parametrów. Zaglądając jednak do dokumentacji dowiemy się, że:

public sealed class ManualResetEvent : EventWaitHandle

public sealed class AutoResetEvent : EventWaitHandle

Obie klasy dziedziczą po EventWaitHandle, którego konstruktor wygląda następująco:

public EventWaitHandle(bool initialState,EventResetMode mode);

public EventWaitHandle(bool initialState,EventResetMode mode,string name);

public EventWaitHandle(bool initialState,EventResetMode mode,string name,out bool createdNew);

public EventWaitHandle(bool initialState,EventResetMode mode,string name,out bool createdNew,EventWaitHandleSecurity eventSecurity);

Tak naprawdę ManualResetEvent oraz AutoResetEvent to wrappery, ułatwiające pracę ze zdarzeniami. Możemy bezpośrednio użyć EventWaitHandle i przekazać  nazwę. Stwórzmy więc proces A, który użyje takiego konstruktora:

internal class Program
{
   public static void Main()
   {
       var manualResetEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "TestEvent");
       Console.WriteLine("Utworzono obiekt EventWaitHandle");
       manualResetEvent.WaitOne();
   }
}

Następnie w procesie B, chcemy odczytać ten obiekt i np. zamknąć aplikację, gdy on istnieje (używaliśmy mutex’ów do tego samego celu):

class Program
{
   static void Main(string[] args)
   {
       try
       {
           EventWaitHandle eventHandle = EventWaitHandle.OpenExisting("TestEvent");
       }
       catch(WaitHandleCannotBeOpenedException)
       {
           Console.WriteLine("Nie udało się otworzyć obiektu.");
           return;
       }
       Console.WriteLine("Obiekt znaleziony.");                
   }
}

Gdy nie ma obiektu o podanej nazwie wyrzucany jest wyjątek WaitHandleCannotBeOpenedException. W przeciwnym wypadku możemy korzystać z niego, tak jakby został on utworzony w tym samym procesie (Set, Wait itp.).

Co jeśli wywołamy kontruktory tworzące ten sam obiekt w dwóch różnych procesach? Tzn.:

// Proces A
internal class Program
{
   public static void Main()
   {
       var manualResetEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "TestEvent");
       manualResetEvent.WaitOne();
       Console.WriteLine("Sygnal odebrany.");
   }
}
// Proces B
class Program
{
   static void Main(string[] args)
   {
       var manualResetEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "TestEvent");
       manualResetEvent.Set();
       Console.WriteLine("Sygnal wyslany.");
   }
}

Proces B, automatycznie otworzy obiekt zainicjalizowany w procesie A.  W wielu przypadkach to jest logiczne i pożądane rozwiązanie ale co w przypadku gdy Proces A utworzył ManualResetEvent, z kolei proces B AutoResetEvent?

// Proces A
internal class Program
{
   public static void Main()
   {
       var manualResetEvent = new EventWaitHandle(false, EventResetMode.ManualReset, "TestEvent");
       manualResetEvent.WaitOne();
       Console.WriteLine("Sygnal odebrany.");
   }
}
// Proces B
class Program
{
   static void Main(string[] args)
   {
       var manualResetEvent = new EventWaitHandle(false, EventResetMode.AutoReset, "TestEvent");
       manualResetEvent.Set();
       Console.WriteLine("Sygnal wyslany.");
   }
}

Z opisanych przyczyn powinniśmy unikać takiej konstrukcji ponieważ nie wiemy, czy zdarzenie zostało utworzone czy otworzone – stąd nie mamy pojęcia o jego typie, stanie itp. Istnieje inny konstruktor, który poinformuje nas, jaka operacja została właściwie wykonana:

public EventWaitHandle(
    bool initialState,
    EventResetMode mode,
    string name,
    out bool createdNew
)

Parametr createNew określa, czy zdarzenie jest nowe czy zostało tylko otworzone. Pamiętajmy, że gdy zdarzenie jest otwierane przez proces B, to nieważne jaki stan (lub typ) przekażemy – zawsze będzie używany ten pierwotny z procesu A.

Leave a Reply

Your email address will not be published.