SynchronizationLockException:无法在此运行时等待监视器。 - Blazor wasm 中 TaskCompletionSource 的解决方法

SynchronizationLockException: Cannot wait on monitors on this runtime. - Workaround for TaskCompletionSource in Blazor wasm

我有一个异步方法,它返回输入表单中输入的用户值。只要用户没有提交输入,异步方法 Task<String> Read() 就应该等待。当用户提交输入表单时,方法 Task Execute(EditContext context) 被触发。因此,只要表单未提交,我就使用 TaskCompletionSource 来阻止 Read 方法(这适用于 wpf 应用程序,我做到了)。


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);
}

但是通过上面的代码我得到:

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)

这看起来像是 razor-wasm 在任务和线程方面的限制的结果。我从这里尝试了解决方法:https://github.com/dotnet/aspnetcore/issues/14253#issuecomment-534118256

通过使用 Task.Yield 但未成功。知道如何解决这个问题吗?

[编辑:]我认为我的主要结论是,razor-wasm(由于单线程限制)不可能成为 运行 一种同步方法(Console.ReadLine()) ,并等待用户输入,而不会阻塞整个应用程序。看起来没有解决方法。唯一的方法是将所有这些同步调用替换为新的异步调用,如 Console.ReadLineAsync().

我检查了您提供的 github link 的代码,发现您正在这样做:

_stringReaderRedirect = new StringReaderRedirect(Read);

其中 Read 是有问题的函数。然后在 StringReaderRedirect 里面你有:

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

然后你这样做:

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

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

所以你阻塞了异步调用,这是有问题的异常的来源。在单线程环境(如 Blazor WASM)中,这样做是一个主要的禁忌。如果您看到的异常没有被抛出,那么您将遇到死锁:唯一的线程 (UI) 被阻塞等待 Read 的结果,而 Read 本身取决于用户输入,因为需要哪个 UI 线程。 blazor github repo,for example.

上有很多类似的问题

顺便说一句,如果您从 UI 线程执行 Read().GetAwaiter().GetResult(),WPF 中也会发生同样的情况。嗯,不一样,因为在 WPF 的情况下,它只会死锁,但也“不会工作”。

所以一路异步,永远不要阻塞主线程,因为它是你拥有的唯一线程。