Puzzle: FPC’s TEventObject, in Windows

This post is kind of an open question. I’m hoping that I can get some comments, any comments, on it 🙂

Let’s set up a situation with a problem, and look at different solutions for addressing the problem. Which one do you think is best, and more importantly, why?

The Setup

There is a multithreaded application, which offloads work items to a worker thread, and uses TEventObject.SetEvent call to signal to the main thread “hey, all done”. Note: the worker thread doesn’t just complete this one task and then exit; it instead sleeps until another work item is generated.

The Problem

We want the main thread to wait for either a Windows message to be received, OR for the worker thread’s event to be signalled. Win32 provides a handy function for doing exactly this: MsgWaitForMultipleObjects with the event object’s handle and with QS_ALLINPUT as the wakemask.

However, FPC’s implementation of TEventObject doesn’t expose the OS handle, so we can’t call Win32 functions on it. (TEventObject inherits from THandleObject, whose reason for existance is encapsulating an operating system handle, however it’s actual “Handle” property is a pointer and the documentation explicitly states TEventHandle is an opaque type and should not be used in user code).

From looking at the actual implementation of TEventObject, you can determine that the Windows Handle needed is the very first member of the structure pointed to by the “Handle” property, however.

TEventObject.WaitFor() is clearly the intended usage, however that would block on that event only, and doesn’t wake up if window messages are received, therefore doesn’t address the situation.

(Note: it makes perfect sense why TEventObject is abstracting the Windows handle; otherwise it would be too easy to break cross-platform source compatibility, which is something FPC tries really hard to maintain. However, in this scenario the project is locked to Win32 for a number of reasons, so we should take advantage of it’s capability to wait for both events and messages without polling if we can.)

The Options

Here are the options, as I see it (I may have missed some!)

  1. Reinvent: Reimplement the whole TEventObject functionality, which will mostly be line-for-line identical except that the windows Handle will be an accessible property instead of hidden behind an opaque pointer type.
  2. Typecast: Apply the typecast of HANDLE(TEventObject.Handle^) to grab the windows handle out of the opaque type, disable the warning, and just hope the implementation behind the opaque type doesn’t change in a future version of FPC.
  3. Monitor: – Create a third thread, whose only purpose in life is to call TEventObject.WaitFor. Then back in the main thread, call MsgWaitForMultipleObjects on the third thread’s TThread.Handle (since it’s implementation is different: the TThread.Handle property IS a directly usable Windows handle without a typecast.
  4. Poll: Call TEventObject.WaitFor with a small timeout of say 10 millseconds, and pump window messages between waits.

Discussion Please!

In my view,:

  1. Reinvent is the safest, but also doesn’t feel great; turning our backs on FPC’s otherwise simple and elegant synchronization objects.
  2. Typecast is the easiest and results in exactly the behaviour that we want, but “it’s not guaranteed safe forever”.
  3. Monitor is safe, but burning an entire extra thread seems like overkill, and maybe worse than polling in #4.
  4. Poll works, but eats a lot of unnecessary context switches if the application is just idling and there’s no work going on. (A Sleep(10) in an idle loop consumes about 1% of an entire logical processor just in context switches)

Would love to hear your thoughts about how you would approach this. Remember the problem isn’t that the problem can’t be resolved (there are at least 4 solutions above, and maybe more I’ve missed), the problem is picking the ‘best’ solution, so your thoughts about why you would recommend a particular approach are the most valuable.

Leave a Reply

Your email address will not be published. Required fields are marked *