Вызовите асинхронный метод в потоке пользовательского интерфейса

2

Я пытаюсь создать клиент WPF с аутентификацией IdentityServer. Я использую их OidcClient чтобы войти в систему. Это полностью асинхронно, в то время как мое приложение синхронизировано и не может быть реорганизовано без огромных усилий. призвание

var result = await _oidcClient.LoginAsync();

не ждет результата. Вызов Wait() или .Result вызывает тупик. Task.Run его другим Task.Run жалуется, что метод не работает в потоке пользовательского интерфейса (он открывает браузер с диалогом входа в систему).

У вас есть идеи, как это решить? Нужно ли писать собственную синхронизацию OidcClient?

  • 1
    Если ваш дизайн требует синхронной блокировки потока пользовательского интерфейса на длительные периоды времени, вам нужно исправить свой дизайн. Это единственный способ получить рабочий код, и чем дольше вы его откладываете, тем сложнее будет его исправить.
  • 0
    вам нужно перенаправить вызов пользовательского интерфейса в Task.Run обратно в поток пользовательского интерфейса, используя метод Invoke в элементе управления. docs.microsoft.com/en-us/dotnet/api/...
Показать ещё 6 комментариев
Теги:
async-await
.net-4.7

1 ответ

3
Лучший ответ

Как и в других подобных случаях, когда вам нужно ввести асинхронность в устаревшее приложение без особого рефакторинга, я бы рекомендовал использовать простой модальный диалог "Пожалуйста, подождите...". Диалог инициирует асинхронную операцию и закрывается, когда операция завершена.

Window.ShowDialog - это синхронный API, который блокирует основной пользовательский интерфейс и возвращается к вызывающей стороне только после закрытия модального диалога. Тем не менее, он по-прежнему запускает вложенный цикл сообщений и перекачивает сообщения. Таким образом, асинхронные обратные вызовы продолжения задачи по-прежнему прокачиваются и выполняются, в отличие от использования склонной к взаимоблокировке Task.Wait().

Вот базовый, но полный пример WPF, _oidcClient.LoginAsync() с помощью Task.Delay() и выполнение его в потоке пользовательского интерфейса, подробности см. В WpfTaskExt.Execute.

Поддержка отмены не является обязательной; если фактическое LoginAsync не может быть отменено, предотвращается преждевременное закрытие диалога.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            var button = new Button() { Content = "Login", Width = 100, Height = 20 };
            button.Click += HandleLogin;
            this.Content = button;
        }

        // simulate _oidcClient.LoginAsync
        static async Task<bool> LoginAsync(CancellationToken token)
        {
            await Task.Delay(5000, token);
            return true;
        }

        void HandleLogin(object sender, RoutedEventArgs e)
        {
            try
            {
                var result = WpfTaskExt.Execute(
                    taskFunc: token => LoginAsync(token),
                    createDialog: () =>
                        new Window
                        {
                            Owner = this,
                            Width = 320,
                            Height = 200,
                            WindowStartupLocation = WindowStartupLocation.CenterOwner,
                            Content = new TextBox
                            {
                                Text = "Loggin in, please wait... ",
                                HorizontalContentAlignment = HorizontalAlignment.Center,
                                VerticalContentAlignment = VerticalAlignment.Center
                            },
                            WindowStyle = WindowStyle.ToolWindow
                        },
                    token: CancellationToken.None);

                MessageBox.Show($"Success: {result}");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }

    public static class WpfTaskExt
    {
        /// <summary>
        /// Execute an async func synchronously on a UI thread,
        /// on a modal dialog nested message loop
        /// </summary>
        public static TResult Execute<TResult>(
            Func<CancellationToken, Task<TResult>> taskFunc,
            Func<Window> createDialog,
            CancellationToken token = default(CancellationToken))
        {
            var cts = CancellationTokenSource.CreateLinkedTokenSource(token);

            var dialog = createDialog();
            var canClose = false;
            Task<TResult> task = null;

            async Task<TResult> taskRunner()
            {
                try
                {
                    return await taskFunc(cts.Token);
                }
                finally
                {
                    canClose = true;
                    if (dialog.IsLoaded)
                    {
                        dialog.Close();
                    }
                }
            }

            dialog.Closing += (_, args) =>
            {
                if (!canClose)
                {
                    args.Cancel = true; // must stay open for now
                    cts.Cancel();
                }
            };

            dialog.Loaded += (_, __) =>
            {
                task = taskRunner();
            };

            dialog.ShowDialog();

            return task.GetAwaiter().GetResult();
        }
    }
}
  • 1
    Спасибо! Этот метод позволяет мне вносить локальные изменения, а не в систему в целом!

Ещё вопросы

Сообщество Overcoder
Наверх
Меню