I'm going to post this hoping that it'll help others avoid my stupid blunders... and hoping that Steven might have a bright idea how plugins can be shielded from this complexity.
What I did:
- For debugging, print each received notificatio to a small window.
- The window runs on MusicBee's main thread.
- Messages are printed by Invoke'ing the textbox control (owned by the main thread).
- I prevented the plugin from running entry points (PluginClosed, NotificationReceived, etc.) concurrently.
Sounds innocent enough, right?
Here's what happens:
- MusicBee gets quit, which gets handled on the main thread.
- MB stops the auto Dj which results in a notification on a separate thread.
- On the worker thread, plugin tries to Invoke a control on the main thread, and Invoke locks.
- Back on the main thread, MB tries to run PluginClosed on the plugin, which conflicts with the blocking Invoke above and deadlocks.
- Alternatively, without the explicit locks, MB finishes shutting down; but when the main thread is free to handle the still waiting Invoke, the control that was originally invoked is now disposed and the invoking thread is thrown an exception.
So, what to do, what to do. In my case, I'll use an asynchronous BeginInvoke and catch the exception, but plugins that want to do some legitimate GUI work might run into problems here. How about if disposing the main window is delayed until plugins have finished closing on their worker thread? That way, the GUI thread could be freed up for plugins to do some last-minute user interaction.