internal static void AddEventHandler <T>(Func <T, EventRegistrationToken> addMethod, Action <EventRegistrationToken> removeMethod, T handler) { // The instanceKey will be IUnknown * of the target object object instanceKey = GetInstanceKey(removeMethod); // Call addMethod outside of RW lock // At this point we don't need to worry about race conditions and we can avoid deadlocks // if addMethod waits on finalizer thread // If we later throw we need to remove the method EventRegistrationToken token = addMethod(handler); bool tokenAdded = false; try { EventRegistrationTokenListWithCount tokens; // // The whole add/remove code has to be protected by a reader/writer lock // Add/Remove cannot run at the same time with cache cleanup but Add/Remove can run at the same time // s_eventCacheRWLock.EnterReadLock(); try { // Add the method, and make a note of the delegate -> token mapping. EventCacheEntry registrationTokens = GetOrCreateEventRegistrationTokenTable(instanceKey, removeMethod); try { registrationTokens.LockAcquire(); // // We need to find the key that equals to this handler // Suppose we have 3 handlers A, B, C that are equal (refer to the same object and method), // the first handler (let's say A) will be used as the key and holds all the tokens. // We don't need to hold onto B and C, because the COM object itself will keep them alive, // and they won't die anyway unless the COM object dies or they get unsubscribed. // It may appear that it is fine to hold A, B, C, and add them and their corresponding tokens // into registrationTokens table. However, this is very dangerous, because this COM object // may die, but A, B, C might not get collected yet, and another COM object comes into life // with the same IUnknown address, and we subscribe event B. In this case, the right token // will be added into B's token list, but once we unsubscribe B, we might end up removing // the last token in C, and that may lead to crash. // object key = FindEquivalentKeyUnsafe(registrationTokens.registrationTable, handler, out tokens); if (key == null) { tokens = new EventRegistrationTokenListWithCount(registrationTokens.tokenListCount, token); registrationTokens.registrationTable.Add(handler, tokens); } else { tokens.Push(token); } tokenAdded = true; } finally { registrationTokens.LockRelease(); } } finally { s_eventCacheRWLock.ExitReadLock(); } #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event subscribed for instance = " + instanceKey + ", handler = " + handler + "\n"); #endif } catch (Exception) { // If we've already added the token and go there, we don't need to "UNDO" anything if (!tokenAdded) { // Otherwise, "Undo" addMethod if any exception occurs // There is no need to cleanup our data structure as we haven't added the token yet removeMethod(token); } throw; } }
private static object FindEquivalentKeyUnsafe(ConditionalWeakTable <object, EventRegistrationTokenListWithCount> registrationTable, object handler, out EventRegistrationTokenListWithCount tokens) { foreach (KeyValuePair <object, EventRegistrationTokenListWithCount> item in registrationTable) { if (Object.Equals(item.Key, handler)) { tokens = item.Value; return(item.Key); } } tokens = null; return(null); }