internal static void RemoveAllEventHandlers(Action <EventRegistrationToken> removeMethod) { object instanceKey = GetInstanceKey(removeMethod); System.Collections.Generic.Internal.List <EventRegistrationToken> tokensToRemove = new System.Collections.Generic.Internal.List <EventRegistrationToken>(); // // 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 { EventCacheEntry registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod); if (registrationTokens == null) { // We have no information regarding this particular instance (IUnknown*/type) - just return // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance return; } try { registrationTokens.LockAcquire(); // Copy all tokens to tokensToRemove array which later we'll call removeMethod on // outside this lock foreach (KeyValuePair <object, EventRegistrationTokenListWithCount> item in registrationTokens.registrationTable) { item.Value.CopyTo(tokensToRemove); } // Clear the table - at this point all event handlers are no longer in the cache // but they are not removed yet registrationTokens.registrationTable.Clear(); #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] Cache cleared for managed instance = " + instanceKey + "\n"); #endif } finally { registrationTokens.LockRelease(); } } finally { s_eventCacheRWLock.ExitReadLock(); } // // Remove all handlers outside the lock // #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] Start removing all events for instance = " + instanceKey + "\n"); #endif CallRemoveMethods(removeMethod, tokensToRemove); #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] Finished removing all events for instance = " + instanceKey + "\n"); #endif }
internal static void RemoveEventHandler <T>(Action <EventRegistrationToken> removeMethod, T handler) { object instanceKey = GetInstanceKey(removeMethod); EventRegistrationToken token; // // 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 { EventCacheEntry registrationTokens = GetEventRegistrationTokenTableNoCreate(instanceKey, removeMethod); if (registrationTokens == null) { // We have no information regarding this particular instance (IUnknown*/type) - just return // This is necessary to avoid leaking empty dictionary/conditionalWeakTables for this instance #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] no registrationTokens found for instance=" + instanceKey + ", handler= " + handler + "\n"); #endif return; } try { registrationTokens.LockAcquire(); EventRegistrationTokenListWithCount tokens; // Note: // When unsubscribing events, we allow subscribing the event using a different delegate // (but with the same object/method), so we need to find the first delegate that matches // and unsubscribe it // It actually doesn't matter which delegate - as long as it matches // Note that inside TryGetValueWithValueEquality we assumes that any delegate // with the same value equality would have the same hash code object key = FindEquivalentKeyUnsafe(registrationTokens.registrationTable, handler, out tokens); Debug.Assert((key != null && tokens != null) || (key == null && tokens == null), "key and tokens must be both null or non-null"); if (tokens == null) { // Failure to find a registration for a token is not an error - it's simply a no-op. #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] no token list found for instance=" + instanceKey + ", handler= " + handler + "\n"); #endif return; } // Select a registration token to unregister // Note that we need to always get the last token just in case another COM object // is created at the same address before the entry for the old one goes away. // See comments above s_eventRegistrations for more details bool moreItems = tokens.Pop(out token); // If the last token is removed from token list, we need to remove it from the cache // otherwise FindEquivalentKeyUnsafe may found this empty token list even though there could be other // equivalent keys in there with non-0 token list if (!moreItems) { // Remove it from (handler)->(tokens) // NOTE: We should not check whether registrationTokens has 0 entries and remove it from the cache // (just like managed event implementation), because this might race with the finalizer of // EventRegistrationTokenList registrationTokens.registrationTable.Remove(key); } #if false BCLDebug.Log("INTEROP", "[WinRT_Eventing] Event unsubscribed for managed instance = " + instanceKey + ", handler = " + handler + ", token = " + token.m_value + "\n"); #endif } finally { registrationTokens.LockRelease(); } } finally { s_eventCacheRWLock.ExitReadLock(); } // Call removeMethod outside of RW lock // At this point we don't need to worry about race conditions and we can avoid deadlocks // if removeMethod waits on finalizer thread removeMethod(token); }
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; } }