Skip to content

[wasi] async/await on WASIp2 #102894

@dicej

Description

@dicej

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/await and 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 on async/await.
  • Support async/await, but disallow use of features such as Task.WhenEach which require ThreadPool as an implementation detail, and possibly provide alternative implementations of those features which are single-thread-friendly.
  • Refactor the parts of System.Threading.Tasks which are not inherently multithreaded (but currently use ThreadPool) to support an alternative, single-threaded deferred execution mechanism on platforms that do not support multithreading.
  • Provide a WASI-specific ThreadPool implementation 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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions