-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
I've been working to support async/await in C# for doing concurrent I/O when targeting WASIp2. A key piece of that is the wasi:io/poll#poll function, which accepts an arbitrary number of pollable handles representing e.g. socket readiness, HTTP protocol events, timer events, etc. It's analogous to the traditional POSIX poll(2) function. My goal is to provide a System.Threading.Tasks-based abstraction on top of wasi:io/poll that supports idiomatic use of async/await, including the various Task combinators such as WhenAny, WhenAll, WhenEach. I've done similar work for Python (using a custom asyncio event loop) and Rust (using a custom async runtime), and am hoping to do the same for .NET.
So far, I've managed to write a custom TaskScheduler which supports simple cases by maintaining a list of Tasks and a list of the Pollables those tasks are awaiting. It has a Run method which, in a loop, alternates between the Task list and the Pollable list, executing the former and calling wasi:io/poll#poll on the latter. That works well for simple cases.
However, I've had trouble building more sophisticated examples using e.g. the new Task.WhenEach combinator due to the somewhat pervasive use of ThreadPool as a deferred execution mechanism throughout the System.Threading.Tasks library code. Given that WASI does not support multithreading and won't for the foreseeable future, ThreadPool's methods currently throw System.PlatformNotSupportedException, which makes it something of a landmine. For example, even though there's nothing inherently multithreaded about Task.WhenEach, the current implementation relies on a ManualResetValueTaskSourceCore with RunContinuationsAsynchronously set to true, which means it always queues continuations using ThreadPool.UnsafeQueueUserWorkItem.
Given that significant pieces of .NET's async/await infrastructure currently relies on multithreading to function, I'm wondering what our options are for WASI. A few come to mind:
- Give up on
async/awaitand use callbacks for concurrency in WASIp2. Besides being un-ergonomic, it would significantly restrict the set of both standard and third-party library features available for use on that platform, given that anything that deals with I/O is presumably either synchronous and multithreaded or asynchronous based onasync/await. - Support
async/await, but disallow use of features such asTask.WhenEachwhich requireThreadPoolas an implementation detail, and possibly provide alternative implementations of those features which are single-thread-friendly. - Refactor the parts of
System.Threading.Taskswhich are not inherently multithreaded (but currently useThreadPool) to support an alternative, single-threaded deferred execution mechanism on platforms that do not support multithreading. - Provide a WASI-specific
ThreadPoolimplementation which simply queues work items without executing them, and provide some public API for running them from the main (and only) thread, e.g. as part of an application event loop.
Thoughts?
I should note that I'm quite new to the .NET ecosystem, so I'm happy to be corrected if I've misunderstood anything.
See also #98957, for which I would consider this issue a prerequisite.