Ответ 1
Существует довольно продолжительное обсуждение COM-квартир и сообщений о перекачке здесь. Но основной интерес представляет собой насос сообщений, используемый для обеспечения правильной сортировки вызовов в STA. Поскольку поток пользовательского интерфейса является рассматриваемой STA, сообщения нужно закачивать, чтобы обеспечить правильное функционирование.
Сообщение WM_DEVICECHANGE может быть отправлено в окно несколько раз. Поэтому в случае, когда вы вызываете GetDrives напрямую, вы фактически получаете рекурсивные вызовы. Поместите точку останова на вызов GetDrives, а затем прикрепите устройство для запуска события.
В первый раз, когда вы попали в точку останова, все в порядке. Теперь нажмите F5, чтобы продолжить, и вы снова попадете в точку останова. На этот раз стек вызовов выглядит примерно так:
[В режиме ожидания, ожидания или соединения] УдалитьMeWindowsForms.exe! УдалитьMeWindowsForms.Form1.WndProc(ref System.Windows.Forms.Message m) Строка 46 С# System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x13 байт
System.Windows.Forms.dll! System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 байт
System.Windows.Forms.dll! System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) + 0x64 байт Переход к управляемому переходному процессу
[Управляемый для коренного перехода]
mscorlib.dll! System.Threading.WaitHandle.InternalWaitOne(System.Runtime.InteropServices.SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + 0x2b байт mscorlib.dll! System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) + 0x2d байт
mscorlib.dll! System.Threading.WaitHandle.WaitOne() + 0x10 байт System.Management.dll! System.Management.MTAHelper.CreateInMTA(тип System.Type) + 0x17b байт
System.Management.dll! System.Management.ManagementPath.CreateWbemPath(строка) + 0x18 байт System.Management.dll! System.Management.ManagementClass.ManagementClass(строка) + 0x29 байт
УдалитьMeWindowsForms.exe! УдалитьMeWindowsForms.Form1.GetDrives() Строка 23 + 0x1b байтов С#
Таким образом, сообщения окна накачиваются, чтобы гарантировать, что COM-вызовы должным образом распределены, но это имеет побочный эффект снова вызвать WndProc и GetDrives (поскольку есть ожидающие сообщения WM_DEVICECHANGE), но все же в предыдущем вызове GetDrives. Когда вы используете BeginInvoke, вы удаляете этот рекурсивный вызов.
Опять положите точку останова на вызов GetDrives и нажмите F5 после первого удара. В следующий раз, подождите секунду или два, а затем снова нажмите F5. Иногда это терпит неудачу, иногда это не так, и вы снова попадете в точку останова. На этот раз ваш столбец будет включать три вызова в GetDrives, причем последний из них инициируется перечислением коллекции diskDriveList. Потому что снова сообщения перекачиваются, чтобы гарантировать, что вызовы маршалируются.
Трудно точно определить, почему срабатывает MDA, но при рекурсивных вызовах разумно предположить, что контекст COM может быть разорван преждевременно и/или объект собирается до того, как освобожденный объект COM может быть освобожден.