79476837

Date: 2025-02-28 22:49:48
Score: 1.5
Natty:
Report link

TL;DR: "If they let you, things would be even worse."


WinForms was made not only to wrap the Windows API, but (originally) to be a thin wrapper over Win32, providing a nice C# PME (Properties, Methods & Events) shape for programming but letting Windows do the heavy lifting as much as possible. This means that much of the threading and synchronization rules derive directly from Win32 or are heavily shaped by Win32. Here, the API set we are talking about is the USER APIs in USER32.lib, aka User32.

Some User32 APIs make direct callbacks while others cause messages to be sent or posted. When calling from non-owning threads, the vast majority or User32 APIs will 'work', but you will get callbacks on the calling thread and messages on the window's thread. The choice of calling thread may also imply a different data set (like a different input queues or lists of Windows, etc) being affected by the call. Finally, while the number was reduced from Win16 to Win32, some APIs that generate messages are in fact using those messages as blocking callbacks.

All of this adds up to a maze of twisty little passages (all different :). Getting the synchronization correct for arbitrary mixes of these, while technically possible, is expert-level work and cannot be done with a simple "lock at your component boundary" strategy*. So:

Other commenters mention reentrancy and deadlocks, with some debate from the peanut gallery. Deadlocks absolutely can happen, as can a wide variety of bugs that boil down to incorrect state mutation when being reentered unexpectedly.

Frameworks have the additional challenge that "calling the system when it won't yield" vs "calling the system when it may or will yield" vs "calling my own app back" are not the same. Windows does not provide APIs that help framework authors integrate super tightly as an "in-between" entity (looking like an app to the system but looking like the system to an app). So, framework authors are in an extra tricky bind when it comes to synchronization.

Most frameworks handle this by trying to protect their app developers from the expert-level dragon's lair lurking below. This usually takes the form of failing certain calls when not on the underlying GUI thread of the object being used.


*NOTE: One could imagine that, armed with a new kind of lock primitive, we could get back to using "a simple lock at your component boundary" even in the face of gnarly Win32-like reentrancy. However, it seems only "low-level client gui framework and windowsystem authors" would be interested in that, and they might be a dying breed :)

Reasons:
  • Long answer (-1):
  • No code block (0.5):
  • User mentioned (1): USER32
  • User mentioned (0): User32
  • User mentioned (0): User32
  • User mentioned (0): User32
  • Low reputation (1):
Posted by: FrancisH