- Should be possible to use async/await in a single- or dual-threaded graphics context.
- Basic implementation can take inspiration therefore from the windows forms and/or wpf synchronization contexts.
- Keep heap allocations down where possible to avoid GC hits.
- Therefore, ValueTask everywhere.
In order to control async/await control flow, two mechanisms can be used:
- Custom
SynchronizationContext
- Custom
GetAwaiter
s
A custom SynchronizationContext
allows propogation of async void
unhandled exceptions and use of BCL synchronization primitives.
Custom GetAwaiter
s can be used for custom synchronization primitives.
The HLU windowing context owns two threads, one for rendering and one for window events. Each thread is associated with a Queue<>
of
structs encapsulating a pair of a SendOrPostCallback
and its associated object
state argument. The Queue<>
can have an initial capacity
set to reduce unneeded allocations when not overloaded with large numbers of work items.
Each thread routine proceeds by repeated draining the queue into a temporary array and processing each work item in order.
The temporary array is needed so new queued items do not prevent non-work-items from processing, such as window events or
the action of swapping buffers and frame timing. Access to the Queue<>
is synchronized with a BreakneckLock
. Each o
the two managed threads has a custom SynchronizationContext
set which queues items to the associated thread's Queue<>
.
Custom Awaiter
structs are implemented, returned by methods on the Window
or View
context, which schedule their continuations
on the relevant thread. Three such awaiters are implemented, one each for scheduling on the window thread, render thread, and worker thread
pool. Additional awaiters may be implemented which schedule work for the next frame or other such constructs, but these are not required or
detailed by this proposal.
Each Awaiter
struct implements both the await-able and the awaiter in one, exposing a single
public SomeAwaiter GetAwaiter() => this;
and then implementing the standard awaiter api surface; see MS docs for that.
Note that the details of the implementation of the queue management may be done in other ways if applicable; for example both
win32 windowing and sdl provide the option to use an existing queue, through the windows dispatcher api in System.Windows.Threading
and the SDL_PushEvent
function respectively, and implementations may queue custom work items to those rather than use a dotnet Queue<>
as appropriate to make handling windowing events easier. Any implementation of this design MUST take care that the process of draining the
work item queue does not prevent windowing events from running.
This design presents the option to allow users to write a single main loop rather than rely on event callbacks, as the async
boundaries
allow native windowing events to process through the above design.