SynchronizationLockException: не может ждать на мониторах в этой среде выполнения. - Обходной путь для TaskCompletionSource в 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);
}

Но с приведенным выше кодом я получаю:

cris: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer [100] Компонент рендеринга необработанного исключения: не может ждать мониторов в этой среде выполнения. System.Threading.SynchronizationLockException: не может ждать мониторов в этой среде выполнения. at (оболочка, управляемая-родной) System.Threading.Monitor.Monitor_wait (object, int) в System.Threading.Monitor.ObjWait (System.Boolean exitContext, System.Int32 millisecondsTimeout, System.Object obj) ‹0x2e64fc8 + 0x00046› in: 0 в System.Threading.Monitor.Wait (System.Object obj, System.Int32 millisecondsTimeout, System.Boolean exitContext) ‹0x2e64ce8 + 0x00022› в: 0 в System.Threading.Monitor.Wait (System.Object obj, System .Int32 миллисекундыTimeout)

Похоже, это результат ограничений razor-wasm в отношении задач и потоков. Я попробовал обходные пути отсюда: https://github.com/dotnet/aspnetcore/issues/14253#issuecomment-534118256

с помощью Task.Yield, но безуспешно. Есть идеи, как обойти эту проблему?

[Edit:] Я думаю, что главный вывод для меня заключается в том, что с помощью razor-wasm (из-за ограничения одного потока) невозможно запустить синхронный метод (Console.ReadLine()) и ждать ввода пользователя без блокировки всего приложения . Похоже, для этого нет никакого обходного пути. Единственный способ - заменить все эти синхронные вызовы новым асинхронным вызовом, например Console.ReadLineAsync().


person Briefkasten    schedule 10.11.2020    source источник
comment
Каждый раз, когда я видел это, это то, где делается звонок.   -  person Brian Parker    schedule 10.11.2020
comment
Разве нельзя вместо этого просто использовать событие, запустить его в Execute и подписаться на него вместо Read?   -  person Evk    schedule 10.11.2020
comment
Метод ReadLine вызывается, скажем, третьей стороной, и мне нужно предоставить ввод пользователя. Я могу создать событие, когда пользователь что-то вводит или когда ReadLine запускается, но тогда мне все равно придется ждать ввода пользователя.   -  person Briefkasten    schedule 10.11.2020
comment
Это просто несовместимое с wasm решение. Я бы тоже не одобрил это в WPF. Вы находитесь в пользовательском интерфейсе, управляемом событиями, используйте события.   -  person Henk Holterman    schedule 10.11.2020
comment
Что ж, тот, кто вызывает это чтение, должен вместо этого подписаться на упомянутое событие (в то время как чтение в этом случае полностью удаляется).   -  person Evk    schedule 10.11.2020


Ответы (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, например .

Кстати, то же самое произошло бы и в WPF, если бы вы сделали Read().GetAwaiter().GetResult() из потока пользовательского интерфейса. Ну, не то же самое, потому что в случае WPF он просто заблокируется, но также не будет работать.

Так что используйте асинхронность до конца и никогда не блокируйте основной поток, поскольку это единственный поток, который у вас есть.

person Evk    schedule 10.11.2020
comment
Спасибо, но в настоящее время я не знаю, как создать такой API. Мне нужно произвести производные от StreamReader и StreamWriter, которые будут получать переадресованные вызовы от Console.WriteLine() и string Console.Readline(). Поэтому я не могу изменить подпись метода Console.Readline. Я попытался удалить GetAwaiter.GetResult и использовать вместо этого цикл внутри Read(), который проверяет, имеет ли поле ввода Command.Text значение. Но это также заблокирует поток. - person Briefkasten; 10.11.2020
comment
А на что вообще ссылается Console.Read \ Write в контексте Blazor WASM? Консоль Javascript? Но вы не можете читать с него (в браузере). - person Evk; 10.11.2020
comment
Это относится к фактическому консольному приложению, которое использует System.Console.WriteLine. Взгляните на github.com/mfe-/Blazor .Console / blob / master / Demo / ConsoleTest.cs. Печать текста с помощью blazor wasm не проблема, но перенаправление пользовательского ввода из blazor wasm обратно в System.Console.ReadLine - сложная часть. - person Briefkasten; 10.11.2020
comment
Да, но так не может быть. Просто замените вызовы встроенного класса Console своим собственным (или добавьте метод расширения ReadAsync во встроенную консоль), и вы не потеряете ничего ценного. Синхронное чтение пользовательского ввода просто не подходит в этом сценарии. - person Evk; 10.11.2020