public void RunTest() { // A thread running the GC. var done = new ManualResetEvent(false); var gcThread = new Thread(() => { while (!done.WaitOne(0)) { Thread.Sleep(100); GC.Collect(); } }) { IsBackground = true, }; gcThread.Start(); // A thread calling retain/release on an object var rrThread = new Thread(() => { var obj = new CustomObject(); while (!done.WaitOne(0)) { obj.DangerousRetain(); obj.DangerousRelease(); } }) { IsBackground = true, }; rrThread.Start(); // A thread that calls a method returning a custom object var itThread = new Thread(() => { var obj = new CustomObject(); while (!done.WaitOne(0)) { Messaging.IntPtr_objc_msgSend(obj.Handle, Selector.GetHandle("getObject")); } }) { IsBackground = true, }; itThread.Start(); // Let the threads run for little bit. 500ms is enough to trigger the deadlock for me pretty much always. // If the process deadlocks, the bug is there. Thread.Sleep(500); done.Set(); gcThread.Join(); rrThread.Join(); itThread.Join(); }