0
votes

I have an asynchronous method which is returning the entered user value of an input form. As long as the user didn't submit the input, the async method Task<String> Read() should be waiting. When the user submits the input form the method Task Execute(EditContext context) gets triggered. Therefore I used the TaskCompletionSource to block the Read method as long the form wasn't submit (which works for the wpf app, I did).


public async Task<String> Read()
{
    StringReadTaskCompletionSource = new TaskCompletionSource<string>();
    return await StringReadTaskCompletionSource.Task;
}
protected Task Execute(EditContext context)
{
    //...
    StringReadTaskCompletionSource
        .SetResult((context?.Model as ConsoleInput as ConsoleInput).Text);
}

But with the above code I get:

crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Cannot wait on monitors on this runtime. System.Threading.SynchronizationLockException: Cannot wait on monitors on this runtime. at (wrapper managed-to-native) System.Threading.Monitor.Monitor_wait(object,int) at System.Threading.Monitor.ObjWait (System.Boolean exitContext, System.Int32 millisecondsTimeout, System.Object obj) <0x2e64fc8 + 0x00046> in :0 at System.Threading.Monitor.Wait (System.Object obj, System.Int32 millisecondsTimeout, System.Boolean exitContext) <0x2e64ce8 + 0x00022> in :0 at System.Threading.Monitor.Wait (System.Object obj, System.Int32 millisecondsTimeout)

It looks like to be the result of the limitations of razor-wasm, regarding tasks and threads. I tried the workarounds from here: https://github.com/dotnet/aspnetcore/issues/14253#issuecomment-534118256

by using Task.Yield but was unsuccessful. Any idea how to workaround this issue?

[Edit:] I think the main conclusion for me is, that it's not possible with razor-wasm (because of the one thread limitation) to run a synchronous method (Console.ReadLine()), and waiting for a user input, without blocking the whole app. It looks like there is no workaround for this. The only way is to replace all this synchronously calls with a new async call like Console.ReadLineAsync().

1
Every time I have seen this its where the call is being made. - Brian Parker
Cannot you just use event instead, fire it in Execute and subscribe to it instead of Read? - Evk
The ReadLine method gets called, lets say by a third party, and I need to provide the user input. I can create a event when the user enters something, or when ReadLine is fired but then I would still need to wait for the user input. - Briefkasten
This just isn't a wasm compatible solution. I would frown on it in WPF too. You are in an event driven UI, use events. - Henk Holterman
Well whoever calls that read should instead subscribe to mentioned event (while read is completely removed in this case). - Evk

1 Answers

3
votes

I've inspected code by the github link you provided and noticed that you are doing this:

_stringReaderRedirect = new StringReaderRedirect(Read);

where Read is function mentioned in question. Then inside StringReaderRedirect you have:

private readonly Func<Task<string>> _ReadRedirectFunc;
public StringReaderRedirect(Func<Task<string>> readredirect) : base("foo")
{
    _ReadRedirectFunc = readredirect;
}

And then you do this:

public override string ReadLine()
{
    //return _ReadRedirectFunc?.Invoke();
    //return base.ReadLine();
    Task<string> task = _ReadRedirectFunc?.Invoke();

    return task?.GetAwaiter().GetResult();
}

So you are blocking on asynchronous call, which is the source of exception in question. Doing this is a major no-no in a single threaded environment, like Blazor WASM. If exception you see were not thrown then you would have a deadlock: the only thread (UI) is blocked waiting for the result of Read, while Read itself depends on user input, for which UI thread is required. There are many similar issues on blazor github repo, for example.

The same would happen in WPF by the way, if you did Read().GetAwaiter().GetResult() from UI thread. Well not the same, because in case of WPF it would just deadlock, but "will not work" also.

So go async all the way and never block main thread, since it's the only thread you have.