Why is the standard C# event invocation pattern thread-safe without a memory barrier or cache invalidation? What about similar code?

后端 未结 5 1791
余生分开走
余生分开走 2021-02-19 03:37

In C#, this is the standard code for invoking an event in a thread-safe way:

var handler = SomethingHappened;
if(handler != null)
    handler(this, e);
         


        
5条回答
  •  借酒劲吻你
    2021-02-19 04:04

    When this is evaluated:

    thing.memberFoo = new Foo(1234);
    

    First new Foo(1234) is evaluated, which means that the Foo constructor executes to completion. Then thing.memberFoo is assigned the value. This means that any other thread reading from thing.memberFoo is not going to read an incomplete object. It's either going to read the old value, or it's going to read the reference to the new Foo object after its constructor has completed. Whether this new object is in the cache or not is irrelevant; the reference being read won't point to the new object until after the constructor has completed.

    The same thing happens with the object pool. Everything on the right evaluates completely before the assignment happens.

    In your example, B will never get the reference to R before R's constructor has run, because A does not write R to Q until A has completed constructing R. If B reads Q before that, it will get whatever value was already in Q. If R's constructor throws an exception, then Q will never be written to.

    C# order of operations guarantees this will happen this way. Assignment operators have the lowest precedence, and new and function call operators have the highest precedence. This guarantees that the new will evaluate before the assignment is evaluated. This is required for things like exceptions -- if an exception is thrown by the constructor then the object being allocated will be in an invalid state and you don't want that assignment to occur regardless of whether you're multithreaded or not.

提交回复
热议问题