Skip to content

Blazor client developers can use Parallel.For in their applications to share more code with server #43411

Closed

Description

@andersson09

Description

Hi,

So the Roslyn compiler is kind of working in blazor wasm with the latest version (3.8.0-4.final) which is great! Appears the hashing issues are fixed. However, now I find there is a strange bug whereby I receive a multi threading exception under the following scenario:

  • Dotnet run
  • Load web page in browser
  • Run compile (works fine)
  • Refresh page in browser
  • Run compile (exception)

Strangely it works if I close the original tab and open a new one.

Thanks

Configuration

  • .NET 5.0.100-rc.2.20479.15
  • Blazor wasm
  • Microsoft.CodeAnalysis 3.8.0-4.final
  • Chrome

Regression?

Last time Roslyn was working in the browser was with Blazor Webassembly 3.2.1 and Microsoft.CodeAnalysis 3.6.0

Other information

System.PlatformNotSupportedException: Cannot wait on monitors on this runtime.
   at System.Threading.Monitor.ObjWait(Boolean exitContext, Int32 millisecondsTimeout, Object obj)
   at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout, Boolean exitContext)
   at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout)
   at System.Threading.ManualResetEventSlim.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.SpinThenBlockingWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.InternalWaitCore(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.InternalWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at System.Threading.Tasks.TaskReplicator.Replica.Wait()
   at System.Threading.Tasks.TaskReplicator.Run[RangeWorker](ReplicatableUserAction`1 action, ParallelOptions options, Boolean stopOnFirstFailure)
   at System.Threading.Tasks.Parallel.ForWorker[Object](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 localInit, Action`1 localFinally)
   at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body)
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CompilationStage stage, Boolean includeEarlierStages, DiagnosticBag diagnostics, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CompilationStage stage, Boolean includeEarlierStages, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CompileMethods(CommonPEModuleBuilder moduleBuilder, Boolean emittingPdb, Boolean emitMetadataOnly, Boolean emitTestCoverageData, DiagnosticBag diagnostics, Predicate`1 filterOpt, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream metadataPEStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, CompilationTestData testData, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable`1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable`1 embeddedTexts, Stream metadataPEStream, CancellationToken cancellationToken)

Activity

  1. danmoseley commented on Oct 14, 2020

    @danmoseley
    Contributor

    I think this is by design -- Roslyn uses mechanisms that rely on mutexes, and those aren't supported on Blazor.
    @marek-safar given there's only one thread in WASM, as I understand it, is there any kind of cooperative multithreading support, such that a mutex could work?

  2. andersson09 commented on Oct 14, 2020

    @andersson09
    Author

    @danmosemsft But why would it work on first page load? Also to note it works when you close the original tab and create a new one.

  3. danmoseley commented on Oct 14, 2020

    @danmoseley
    Contributor

    I am guessing that if no task is already in progress, it has a special path that does not bother to check the mutex.

  4. marek-safar commented on Oct 15, 2020

    @marek-safar
    Contributor

    Although System.Threading.Monitor.Wait is unsupported API on wasm I'm reluctant to simply mark the issue as by design. We have Parallel.For implementation which does not work on wasm with no warning to the developers which is not great.

    There two different solutions I can think of

    • We could fix Parallel.For implementation not to use any threads in a single thread environment and this config option for single CPU setups
    • We could mark the API as unsupported on single thread platforms (this is easy for wasm but what about others?)

    @jeffhandley @buyaa-n I'm concern that #43363 did catch this issue. Could you please investigate why?

  5. andersson09 commented on Oct 15, 2020

    @andersson09
    Author

    @marek-safar I think it would be beneficial to go with the first option (of course I would say that :) ). The roslyn api is so close to working perfectly under wasm and I think a significant number of users are using this api within the browser. Would marking the API as unsupported mean even the features that were working wouldn't work?

    Perhaps adding an option as an input parameter to disable multithreading could also work?

  6. 34 remaining items

  7. changed the title [-]Blazor wasm app should be able to use Parallel.For[/-] [+]Blazor client developers can use Parallel.For in their applications to share more code with server[/+] on Nov 27, 2020
  8. added
    Priority:2Work that is important, but not critical for the release
    on Nov 27, 2020
  9. RChrisCoble commented on Nov 30, 2020

    @RChrisCoble

    It almost sounds like you should just support this instead:

    Real multithreading (on supported browsers) #17730

  10. ikeough commented on Dec 2, 2020

    @ikeough

    Our issue is exactly the opposite of @andersson09's, and happens in a different place, but seems to have the same root cause. Our call to CSharpCompilation.CreateScriptCompilation succeeds but the first time we try to do CSharpCompilation.GetDiagnostics following the compilation call we get the Cannot wait on monitors... exception (stack trace below). The second time we execute this exact same code path, it succeeds without throwing an exception.

    The solutions proposed above to "fix" Parallel.For seem like they would fix this issue as well, but it would be nice to have a work-around. I see the work-around proposed here, but that doesn't help those of us who aren't calling Parallel.For directly. Perhaps the ability to pass through the ParallelOptions for all the CSharpCompilation methods that internally use Parallel.For, would enable us to use the proposed work-around.

    Cannot wait on monitors on this runtime.
    blazor.webassembly.js:1    at System.Threading.Monitor.ObjWait(Boolean exitContext, Int32 millisecondsTimeout, Object obj)
    blazor.webassembly.js:1    at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout, Boolean exitContext)
    blazor.webassembly.js:1    at System.Threading.Monitor.Wait(Object obj, Int32 millisecondsTimeout)
    blazor.webassembly.js:1    at System.Threading.ManualResetEventSlim.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at System.Threading.Tasks.Task.SpinThenBlockingWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at System.Threading.Tasks.Task.InternalWaitCore(Int32 millisecondsTimeout, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at System.Threading.Tasks.Task.InternalWait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at System.Threading.Tasks.Task.Wait()
    blazor.webassembly.js:1    at System.Threading.Tasks.TaskReplicator.Replica.Wait()
    blazor.webassembly.js:1    at System.Threading.Tasks.TaskReplicator.Run[RangeWorker](ReplicatableUserAction`1 action, ParallelOptions options, Boolean stopOnFirstFailure)
    blazor.webassembly.js:1    at System.Threading.Tasks.Parallel.ForWorker[Object](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 localInit, Action`1 localFinally)
    blazor.webassembly.js:1    at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body)
    blazor.webassembly.js:1    at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CompilationStage stage, Boolean includeEarlierStages, DiagnosticBag diagnostics, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CompilationStage stage, Boolean includeEarlierStages, CancellationToken cancellationToken)
    blazor.webassembly.js:1    at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CancellationToken cancellationToken)
    blazor.webassembly.js:1    at ElementsWasm.Pages.Model.TryCompile(String source, CSharpCompilation& compilation, Assembly& assembly, List`1& errorDiagnostics) in /Users/ikeough/Documents/ElementsWasm/Pages/Model.razor:line 143
    
  11. ivan-prodanov commented on Mar 3, 2021

    @ivan-prodanov

    Guys, could you please help me find out how this issue was fixed? I sense this was fixed specifically and only in Blazor.

    My non-blazor wasm app has the same issue as OP. If I move the code to a blazor app it works just fine. In my non-blazor wasm app I get the same error "Cannot wait on monitors on this runtime.".

    Was the issue addressed in Roslyn itself? Where's the fix?

  12. danmoseley commented on Mar 3, 2021

    @danmoseley
    Contributor
  13. marek-safar commented on Mar 4, 2021

    @marek-safar
    Contributor

    The issue is not yet fixed. It's planned for .NET6 release with Priority 2 (see the labels).

  14. ivan-prodanov commented on Mar 5, 2021

    @ivan-prodanov

    @marek-safar @lewing It most certainly is fixed in Blazor .NET 5, because you can use Roslyn. I've checked it, it does use Parallel.For inside GetDiagnostics (concurrentBuild option on CSharpCompilationOptions is true).

    The easiest way to reproduce this without Roslyn is to call this C# code anywhere from js "Parallel.For(0, 10, i => Console.WriteLine(i));". It works only in Blazor apps.

  15. modified the milestones: 6.0.0, 7.0.0 on Aug 17, 2021
  16. lewing commented on Aug 17, 2021

    @lewing
    Member

    We've landed an implementation in #57283 once @kg has opened a tracking issue for the follow-up this can be closed.

  17. lewing commented on Aug 17, 2021

    @lewing
    Member
  18. ghost locked as resolved and limited conversation to collaborators on Sep 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Priority:2Work that is important, but not critical for the releaseUser StoryA single user-facing feature. Can be grouped under an epic.arch-wasmWebAssembly architecturearea-System.Threading.Tasks

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions