【问题标题】:SynchronizationLockException: Cannot wait on monitors on this runtime. - Workaround for TaskCompletionSource in Blazor wasmSynchronizationLockException:无法在此运行时的监视器上等待。 - Blazor wasm 中 TaskCompletionSource 的解决方法
【发布时间】:2021-02-22 14:02:47
【问题描述】:

我有一个异步方法,它返回输入表单的输入用户值。只要用户没有提交输入,异步方法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);
}

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

暴击:Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] 未处理的异常呈现组件:无法等待此运行时的监视器。 System.Threading.SynchronizationLockException:无法等待此运行时的监视器。 在(包装器托管到本机) System.Threading.Monitor.Monitor_wait(object,int) 在 System.Threading.Monitor.ObjWait (System.Boolean exitContext, System.Int32 毫秒超时, System.Object obj) in :0 在 System.Threading.Monitor.Wait(System.Object obj,System.Int32 毫秒超时,System.Boolean exitContext) 在:0 在 System.Threading.Monitor.Wait (System.Object obj, System.Int32 毫秒超时)

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

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

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

【问题讨论】:

  • 每次我看到这个都是在打电话的地方。
  • 你不能只使用事件,而是在执行中触发它并订阅它而不是Read
  • ReadLine 方法被调用,假设由第三方调用,我需要提供用户输入。我可以在用户输入内容时创建一个事件,或者当 ReadLine 被触发但我仍然需要等待用户输入。
  • 这不是一个兼容 wasm 的解决方案。我也会在 WPF 中对它皱眉。你在一个事件驱动的 UI 中,使用事件。
  • 好吧,调用 read 的人应该订阅提到的事件(而在这种情况下 read 被完全删除)。

标签: c# multithreading async-await blazor blazor-webassembly


【解决方案1】:

我通过您提供的 github 链接检查了代码,并注意到您正在这样做:

_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 的情况下它只会死锁,但也“不起作用”。

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

【讨论】:

  • 谢谢,但目前我不知道如何设计这样的api。我需要从 StreamReader 和 StreamWriter 派生,它们将接收来自Console.WriteLine() string Console.Readline() 的转发呼叫。因此我不能修改 Console.Readline 的方法签名。我试图删除 GetAwaiter.GetResult 并使用 Read() 内部的循环来检查输入字段 Command.Text 是否有值。但这也会阻塞线程。
  • Console.Read\Write 甚至在 Blazor WASM 的上下文中指的是什么? Javascript控制台?但是您无法读取它(在浏览器中)。
  • 它指的是使用 System.Console.WriteLine 的实际控制台应用程序。请查看github.com/mfe-/Blazor.Console/blob/master/Demo/ConsoleTest.cs。使用 blazor wasm 打印文本没有问题,但是将用户输入从 blazor wasm 重定向回 System.Console.ReadLine 是棘手的部分。
  • 是的,但这不能这样工作。只需用您自己的替换对内置控制台类的调用(或将扩展方法 ReadAsync 添加到内置控制台),您就不会失去任何有价值的东西。同步读取用户输入只是不适合这种情况。
猜你喜欢
  • 2023-03-13
  • 2014-02-19
  • 1970-01-01
  • 2022-11-24
  • 1970-01-01
  • 1970-01-01
  • 2021-10-10
相关资源
最近更新 更多