Search code examples
c#.netmultithreadingsynchronizationevent-wait-handle

Is it possible to restrict set/reset of EventWaitHandle?


I would like to have EventWaitHandle object (eg. ManualResetEvent) that can be set/reset from only one place but that can be waited for (with WaitOne()) from multiple places. Put it differently, I want to have just one class that can set/reset it while all other classes to be able to call WaitOne() on it. It's kind of like regular "read-only" property:

private string MyReadOnlyText {get; private set;}

only for specific methods in ManualResetEvent. But not the:

private ManualResetEvent MyEvent {get; private set;}

Of course, this won't work because, while only owner class can instantiate this property, any other external object is able to change it with MyEvent.Set/MyEvent.Reset().

Is something like this even possible? The purpose of this is to prevent arbitrary objects in the application to manipulate the wait handle state and to be sure that this can be done just from the single place.


Solution

  • To some extent, this seems like something that can be addressed by convention, i.e. just stipulate that the handle should be considered "read-only" by consumers. A simple grep through the source code should help ensure against miscreants.

    There is limited support for the concept of a "real-only" event, if you expose the object only as a WaitHandle. WaitHandle instances don't have a Set() method, so that helps remind callers to not mess with it.

    Of course, callers can still cast it to its actual type, and there are static methods in the WaitHandle that still allow manipulation. So it's not like this is 100% assurance. But it would help (and provide for more distinctive patterns to grep for when you do your code audit).

    Finally, the "wrapper" approach mentioned in the comments can work as well. This is the most robust, and is a relatively common pattern for any kind of this sort of "restrict access to members except the owner object".

    If the callers are in a different assembly as the privileged code, then the easiest way to do this is to make the hidden stuff "internal" and the public stuff "public. If they are in the same assembly, then if you can put the hidden members in the same class with the privileged code, then of course they can just be private to that class.

    Often though, you want to encapsulate the special-accessibility object as a separate class while still granting privileged access to one specific class and limiting access by everyone else. In this case, you can use nested classes to achieve this separation, with the outer class containing the privileged code, and the nested class(es) containing the hidden code.

    One version of this approach uses a base/sub-class design, where the base class has the public members and the sub-class has the hidden code:

    class A
    {
        public class B
        {
            protected ManualResetEvent _event = new ManualResetEvent(false);
    
            public void Wait() { _event.Wait(); }
        }
    
        private class C : B
        {
            public void Set() { _event.Set(); }
        }
    
        public B GetAnInstanceofB() { return new C(); }
    
        private void DoSomethingWithB(B b) { ((C)b).Set(); }
    }
    

    Now callers can call the B.Wait() on the instance returned, but they can't cast to the type C to access the Set() method. But note that the code in class A is permitted to (because it can cast the type to C).

    Note that the class B does not itself need to be nested. Just C.

    Another variation on this theme is to declare an interface containing only the public methods, and then have the single implementer of the interface. Again, the implementing class will be nested private to the privileged code, while the interface can be nested or not.

    Finally note that all of this is a matter of code reliability and maintenance. It is always of course possible for code to use reflection to inspect and access any part of any type it wants.

    At the end of the day, you have to think about the audience of the code. If it's a small team and the code is all completely self-contained to a single project, it's probably sufficient to just tell everyone "you can wait on this, but don't muck with this event". In a larger environment, maybe several teams all working on several related projects together but still basically a self-contained unit, using WaitHandle might be sufficient. If you're writing a library for which you intend broad usage, encapsulation and private members and classes is an excellent tool. :)