Тема не по самому UI, а скорее по его архитектуре, поэтому создал в .NET, но если нужно перенесите в .NET GUI
Имеет ли право на жизнь такое использование async/await:
Модальный диалог:
class ModalDialog<TItem> {
private TaskCompletionSource<TItem> taskSource = new TaskCompletionSource<TItem>();
public Task<Result> ExecuteModal() {
this.Show();
return taskSource.Task;
}
private void OkButtonClicked() {
taskSource.SetResult(state);
}
}
Его вызов:
class SomeView {
private async void GetUserSelection() {
var dialog = new ModalDialog { ... };
var userSelection = await dialog.ExecuteModal();
// Doing something with result
}
}
То есть правильно ли ожидать закрытия диалога через async/await? Вроде по тестам дедлока не происходит. И если чуть усложнить вопрос: может ли произойти дедлок, если вызов taskSource.SetResult был сделан через синхронизацию с потоком UI из другого потока:
private async void OnMessageArrived() { // Вызывается не из потока ui
InvokeAsync(() => { // Передаём выполнение в поток UI
taskCompletionSource.SetResult(someResult); // сигнализируем о завершении диалога
});
}
И, кстати, обязательно ли в таком случае переводить выполнение в поток UI, или же Task сам выберет правильный поток, из какого-бы потока не вызывали SetResult?
Здравствуйте, Somescout, Вы писали:
S>И, кстати, обязательно ли в таком случае переводить выполнение в поток UI, или же Task сам выберет правильный поток, из какого-бы потока не вызывали SetResult?
Да, обязательно. Иначе taska будет выполняться в случайном потоке.
S>Имеет ли право на жизнь такое использование async/await:
Имхо, работать-то будет, но целесообразность как-то сомнительна.
Диалоговое окно — это по определению что-то блокирующее основной UI, какой здесь смысл в асинхронщине?
Если только у тебе этих окошек открывается по несколько штук одновременно.
S>То есть правильно ли ожидать закрытия диалога через async/await? Вроде по тестам дедлока не происходит...
Дедлока не будет, но возможен unhandled exception, который не перехватится через try/catch (async void).
Если бы сигнатура была такой async Task GetUserSelection, то дедлок был бы возможен, в зависимости от того, как вызывается метод.
S>И, кстати, обязательно ли в таком случае переводить выполнение в поток UI, или же Task сам выберет правильный поток, из какого-бы потока не вызывали SetResult?
Имхо, не обязательно (если, конечно GetUserSelection, из ui потока изначально вызывается).
private async void GetUserSelection() {
var dialog = new ModalDialog { ... };
var userSelection = await dialog.ExecuteModal();
// Это раскроется во что-то типа такогоvar sc = SyncronizationContext.Current; // Захватили UI-ный контекст синхронизацииvar task = dialog.ExecuteModal();
task.GetAwaiter().OnCompleted = () => {
sc.Post(()=>{ // Исполняем continuation на UI-ном контекстеvar userSelection = task.Result;
// Doing something with result
});
}
}
}
Здравствуйте, Somescout, Вы писали:
S>Тема не по самому UI, а скорее по его архитектуре, поэтому создал в .NET, но если нужно перенесите в .NET GUI
S>Имеет ли право на жизнь такое использование async/await:
S>Модальный диалог: S>
Здравствуйте, ksg71, Вы писали:
K>а вот тут вызов Show (ShowModal) по идее всегда блокирующий, иначе как модальность гарантировать. K>и всегда этот таск уже будет возвращен выполненным
K>
Он возвращает Task, который ожидает вызывающий метод. taskSource.SetResult вызывается когда пользователь завершает диалог, тогда ожидание диалога заканчивается и вызывающий метод получает его результат.
Здравствуйте, RushDevion, Вы писали: S>>Имеет ли право на жизнь такое использование async/await: RD>Имхо, работать-то будет, но целесообразность как-то сомнительна. RD>Диалоговое окно — это по определению что-то блокирующее основной UI, какой здесь смысл в асинхронщине? RD>Если только у тебе этих окошек открывается по несколько штук одновременно.
Смотрите, есть страница на Blazor, когда пользователь делает что-то, нужно запросить у него дополнительные данные, открывается модальный диалог (текущая страница не меняется), в нём запрашиваются данные. Когда диалог завершается, действие продолжается:
private async void OnDoubleClick(DhcpServerLease lease)
{
var newIp = await ModalService.ShowModal<MM.Pages.Modals.AssignAddress, string>("Назначение нового IP-адреса", new Dictionary<string, object>
{
{ "IpAddress", lease.Address },
});
if (newIp != default(string))
{
Device.ReassignHost(lease.Address, newIp);
await Device.ReloadContextAsync();
}
}
RD>Дедлока не будет, но возможен unhandled exception, который не перехватится через try/catch (async void). RD>Если бы сигнатура была такой async Task GetUserSelection, то дедлок был бы возможен, в зависимости от того, как вызывается метод.
Дело в том, что сам метод не содержит ожидания действий, поэтому ему самому асинхронность не нужна (код костыльный, всё ещё размышляю как его переработать).
Немного кода
public Task<ResultType> ShowModal<ComponentType, ResultType>(string title, Dictionary<string, object> parameters = null) where ComponentType : ComponentBase, IModalDialog<ResultType>
{
if (OnShowDialog == null)
{
throw new InvalidOperationException("ModalHost is not instantiated");
}
var taskSource = new TaskCompletionSource<ResultType>();
IModalDialog<ResultType> modalDialog = null;
var content = new RenderFragment(rf =>
{
int idx = 1;
rf.OpenComponent(idx++, typeof(ComponentType));
if (parameters != null)
{
foreach (var param in parameters)
{
rf.AddAttribute(idx++, param.Key, param.Value);
}
}
rf.AddComponentReferenceCapture(idx++, component => {
modalDialog = component as IModalDialog<ResultType>;
modalDialog.DialogHasChanged = DialogHasChanged;
modalDialog.CompleteModal = state =>
{
taskSource.TrySetResult(state ? modalDialog.GetDialogResult() : default(ResultType));
OnClose?.Invoke();
};
});
rf.CloseComponent();
});
OnShowDialog(title,
content,
state => { taskSource.TrySetResult(state ? modalDialog.GetDialogResult() : default(ResultType)); },
() => modalDialog?.ValidateDialog() ?? false
);
return taskSource.Task;
}
Кстати, может подскажите можно ли сделать автоматический вывод вывод ResultType для этого метода? То есть чтобы можно было вызвать:
await ModalService.ShowModal<MM.Pages.Modals.AssignAddress>("Назначение нового IP-адреса", new Dictionary<string, object>{...});
вместо
await ModalService.ShowModal<MM.Pages.Modals.AssignAddress, string>("Назначение нового IP-адреса", new Dictionary<string, object>{...});
Класс определён как,
class AssignAddress: ComponentBase, IModalDialog<string> {...}
создавать экземпляр не вариант. RD>Имхо, не обязательно (если, конечно GetUserSelection, из ui потока изначально вызывается).
Спасибо, проверю.
Здравствуйте, Somescout, Вы писали:
S>Здравствуйте, ksg71, Вы писали:
K>>а вот тут вызов Show (ShowModal) по идее всегда блокирующий, иначе как модальность гарантировать. K>>и всегда этот таск уже будет возвращен выполненным
K>>
S>Он возвращает Task, который ожидает вызывающий метод. taskSource.SetResult вызывается когда пользователь завершает диалог, тогда ожидание диалога заканчивается и вызывающий метод получает его результат.
он вернет его когда this.Show() отработает, то есть ExecuteModal блокирующий и вовращает всегда завершенный таск
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx
S>>Он возвращает Task, который ожидает вызывающий метод. taskSource.SetResult вызывается когда пользователь завершает диалог, тогда ожидание диалога заканчивается и вызывающий метод получает его результат.
K>он вернет его когда this.Show() отработает, то есть ExecuteModal блокирующий и вовращает всегда завершенный таск
Внешний метод написан так:
private async void OnDoubleClick(DhcpServerLease lease)
{
var newIp = await ModalService.ShowModal<MM.Pages.Modals.AssignAddress, string>("Назначение нового IP-адреса", new Dictionary<string, object>
{
{ "IpAddress", lease.Address },
});
if (newIp != default)
{
...
}
}
То есть возвращается задача, и внешний метод ожидает (асинхронно) её завершения. Завершается она по вызову taskSource.SetResult.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Somescout, Вы писали:
S>>Тема не по самому UI, а скорее по его архитектуре, поэтому создал в .NET, но если нужно перенесите в .NET GUI
S>Вот тут случайно не оно? S>Sync -> Async
Не, у меня пока до такого не дошло Пока с более простыми вещами разбираюсь.
S>>>Он возвращает Task, который ожидает вызывающий метод. taskSource.SetResult вызывается когда пользователь завершает диалог, тогда ожидание диалога заканчивается и вызывающий метод получает его результат.
K>>он вернет его когда this.Show() отработает, то есть ExecuteModal блокирующий и вовращает всегда завершенный таск
S>Внешний метод написан так: S>
S>Смотрите, есть страница на Blazor, когда пользователь делает что-то, нужно запросить у него дополнительные данные, открывается модальный диалог (текущая страница не меняется), в нём запрашиваются данные. Когда диалог завершается, действие продолжается: S>
Прежде всего, то что я писал выше про SyncronizationContext относится к WinForms/WPF/ASP.NET.
Я не работал с Blazor и не знаю его специфики.
Может там вообще никакой SyncronizationContext не используется.
Но почему не написать проще:
private void OnDoubleClick(DhcpServerLease lease)
{
var newIp = new AssignAddressDialog().Show();
if (newIp != default(string))
{
Device.ReassignHost(lease.Address, newIp);
Device.ReloadContextAsync(); // Запустили таску и вышли из обработчика, пусть рефрешится в фоне
}
}
Здравствуйте, RushDevion, Вы писали:
RD>Прежде всего, то что я писал выше про SyncronizationContext относится к WinForms/WPF/ASP.NET. RD>Я не работал с Blazor и не знаю его специфики. RD>Может там вообще никакой SyncronizationContext не используется. RD>Но почему не написать проще: RD>
RD>private void OnDoubleClick(DhcpServerLease lease)
RD>{
RD> var newIp = new AssignAddressDialog().Show();
RD> if (newIp != default(string))
RD> {
RD> Device.ReassignHost(lease.Address, newIp);
RD> Device.ReloadContextAsync(); // Запустили таску и вышли из обработчика, пусть рефрешится в фоне
RD> }
RD>}
RD>
Потому что Show покажет диалог и сразу вернёт управление, не дожидаясь результата. Blazor это, по-сути, веб с наворотами (конкретнее — с вынесением логики на сервер), соответственно дождаться завершения диалога можно либо по коллбэку, либо через await.
Здравствуйте, Somescout, Вы писали:
S>Тема не по самому UI, а скорее по его архитектуре, поэтому создал в .NET, но если нужно перенесите в .NET GUI
S>Имеет ли право на жизнь такое использование async/await:
Имеет.
S>То есть правильно ли ожидать закрытия диалога через async/await? Вроде по тестам дедлока не происходит. И если чуть усложнить вопрос: может ли произойти дедлок, если вызов taskSource.SetResult был сделан через синхронизацию с потоком UI из другого потока:
Дедлоки ты заметишь, а вот что у тебя UI-поток постоянно дергается твоим кодом для выполнения мелкой работы между асинхронными вызовами — нет. Или что у тебя твои асинхронные велосипеды допускают многократное вхождение: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/handling-reentrancy-in-async-apps
S>И, кстати, обязательно ли в таком случае переводить выполнение в поток UI, или же Task сам выберет правильный поток, из какого-бы потока не вызывали SetResult?
За это отвечает только вызывающий код. Любой код, вызываемый из UI-потока, должен избавляться от контекста синхронизации как можно раньше (ConfigureAwait(false)). Иначе твой код будет постоянно нагружать UI-поток ненужной работой. Чем меньше работы ты выполняешь через UI-поток, тем лучше.
Короче, тем меньше экспериментировать с async-await, тем меньше багов будет допущено, которых ты просто не заметишь. Впрочем, я именно так опыта набирался , но баги я всё-таки ловить умею.
S>То есть правильно ли ожидать закрытия диалога через async/await? Вроде по тестам дедлока не происходит. И если чуть усложнить вопрос: может ли произойти дедлок, если вызов taskSource.SetResult был сделан через синхронизацию с потоком UI из другого потока: S>
S> private async void OnMessageArrived() { // Вызывается не из потока ui
S> InvokeAsync(() => { // Передаём выполнение в поток UI
S> taskCompletionSource.SetResult(someResult); // сигнализируем о завершении диалога
S> });
S> }
S>
S>И, кстати, обязательно ли в таком случае переводить выполнение в поток UI, или же Task сам выберет правильный поток, из какого-бы потока не вызывали SetResult?
На ксамарине во всяком случае после taskCompletionSource.SetResult(someResult) выполняется в том же потоке из которого вызывается этот SetResult.
Но так как у меня дальше шли await я особо и не парился. https://docs.microsoft.com/ru-ru/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern
см AsyncProducerConsumerCollection<T>
Но вот что касается контекста синхронизации, то по идее
await dialog.ExecuteModal() должен выполнится в потоке UI если не добавлен ConfigureAwait(false); https://ru.stackoverflow.com/questions/681382/Использование-configureawaitfalse
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Somescout, Вы писали:
S>Здравствуйте, ksg71, Вы писали:
K>>формально выглядит так (для вызывающего кода), но внутри зависит от реализации Show
S>Show просто добавляет классы к элементу — сам о не блокирующий.
ну а если кто-нибудь захочет Wait вызвать?
на вашем таске ведь не написано что этого лучше не делать
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx