-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[wasm-mt] Add a System.Runtime.InteropServices.JavaScript.BrowserSynchronizationContext #69409
Comments
Tagging subscribers to 'arch-wasm': @lewing Issue DetailsBackground and motivationIn order to enable multi-threaded WebAssembly programming, we want to allow users to continue to program using In multi-threaded WebAssembly, certain operations (for example code that manipulates the DOM of the current page) must run on the browser's main event loop, not in a WebWorker. As a result, it is necessary to add a API Proposalusing System.Threading;
namespace System.Runtime.InteropServices.JavaScript;
/// A synchronization context that queues work to run on the browser's main thread.
/// In a multi-threaded web application certain operations (such as manipulating the DOM) must be done on the
/// main thread and not in a WebWorker. The BrowserSynchronizationContext allows asynchronous callbacks to
/// return to run their continuations on the main thread.
[SupportedOSPlatform("browser")]
public sealed class BrowserSynchronizationContext : SynchronizationContext
{
public BrowserSynchronizationContext();
public override SynchronizatonContext CreateCopy();
public override void Post(SendOrPostCallback d, object state);
//// Always throws NotSupportedException
public override void Send(SendOrPostCallback d, object state);
} API UsageIn a .razor page in Blazor: <button @onclick="DoSomeWork">Start Work</button>
<text>Result is: @output</text>
@code {
string output = "";
public async Task DoSomeWork() {
output = "pending";
Debug.Assert (SynchronizationContext.Current instanceof BrowserSynchronizationContext);
var result = await Task.Run(Model.SomeAsyncWork); // start some work on the threadpool
output = result.ToString(); // return to the UI thread and update the model
}
} Alternative Designs
We can also consider some other names:
Other considerations:
RisksNo response
|
Who installs the synchronization context? I'm wondering if it needs to be public or if it can just be an implementation detail; most of them are the latter. |
Oh interesting, I hadn't realized that. I think that's reasonable for the main thread. I guess if we later decide to add a synchronization context that will post work to any web worker's event loop, that one would probably need to be public (or at least we might want to add some method that takes a Going to rewrite this issue to focus on just adding an internal synch context that is installed by the runtime |
@kg Moving this to 8.0 . Please change it back, if that's incorrect. |
I think this needs to be in 7. |
Fixed by #72652 |
@stephentoub Hi Stephen, I've been looking into the little hidden interactions between TaskContinuation and custom synchronization contexts and it's very unclear to me how we should go about properly integrating everything here. We need a custom sync context (or something that achieves the equivalent) because things like WebSockets and timers have strong thread affinity in the browser. But from doing testing on my own and reading the code, it looks like having a sync context registered at all will punt all running workloads into a degraded mode where they never run continuations inline. Is this less of a problem than it appears? |
Can you share an example of the kind of code you're concerned about with: If the continuation must run back on the original context, then the implementation has little choice but to dutifully post back to the original context. If the antecedent operation completes on the same context, however, there is an optimization in the await infrastructure that will avoid posting back; that just requires that |
(If instead the issue is that you see continuations being queued back to the original context but doing so is unnecessary, that's why .ConfigureAwait(false) exists.) |
The issue is that in my testing, this logic results in all continuations being posted to the threadpool even if they were ConfigureAwait(false), specifically because we have a sync context. The presence of a sync context is being used as a heuristic for "continuations should never run here". |
Ah, yes, that old gem. Though to clarify slightly, it's really "should never run here if the continuation isn't associated with that same sync context". In general I wouldn't expect it to be a big deal because you should only hit that on the first continuation in a series. After that, subsequent awaits won't see the original sync context because you won't be on a thread that has it set. Unless, are you setting that sync context on the thread pool threads? |
OK, my hope was that it would only hurt the first thing in the series so it wouldn't matter much - I just see so much ContinueAwait(false) in the BCL that this had me worried we were going to significantly regress performance by introducing a sync context and it made me wonder if we should treat it as a radioactive type. But I don't see any real alternative to SyncContext for making sure things with thread affinity can make it back to the main browser thread. Things are weird as-is since right now the wasm 'thread pool' runs everything on the main thread, so we get to skip all the overhead this introduces until threads get turned on and suddenly there's a sync context and a thread pool... And 'if the continuation isn't associated with that same sync context' in this case means 'if the continuation was issued from another sync context OR has no captured context because it was ConfigureAwait(false), it can't run here'? |
Correct. This was put in a decade ago as a defense against doing too much unrelated work on the UI thread of WinForms and WPF client apps. If we were doing it over, I don't think we'd add it, but I expect we'd see a fair number of breaks if we removed the checks now (we could consider it, or consider it only for wasm if it really proved problematic). As an aside, when we added support for |
@kg I think this issue is done. Feel free to reopen, or create new issues if we need to revisit |
Update this is no longer a public API proposal. instead, we should make an
internal
SynchronizationContext subclass that is installed by the runtime on the main thread.Background and motivation
In order to enable multi-threaded WebAssembly programming, we want to allow users to continue to program using
async
/await
. In the current single-threadedbrowser-wasm
runtime, all asynchronous tasks run on the main browser thread implicitly as there is nowhere else to run them. Our multi-threaded WebAssembly programming model is built on WebWorkers - dedicated threads that do not share system memory with the main browser thread but may pass messages to it back and forth including WebAssembly main memory using a SharedArrayBuffer.In multi-threaded WebAssembly, certain operations (for example code that manipulates the DOM of the current page) must run on the browser's main event loop, not in a WebWorker. As a result, it is necessary to add a
SynchronizationContext
subclass that allows asynchronous work to be posted back to the main browser thread.API Proposal
API Usage
In a .razor page in Blazor:
Alternative Designs
We can also consider some other names:
DOMSynchronizationContext
MainEventLoopSynchronizationContext
JavaScriptSynchronizationContext
Other considerations:
Risks
No response
The text was updated successfully, but these errors were encountered: