繁体   English   中英

在UI线程上调用异步方法

[英]Call async method on UI thread

我正在尝试使用IdentityServer身份验证创建WPF客户端。 我正在使用他们的OidcClient登录。当我的应用程序同步时,它是完全异步的,并且无需大量精力就无法重构。 调用

var result = await _oidcClient.LoginAsync();

不等待结果。 调用Wait().Result会导致死锁。 将它包装到其他Task.Run是抱怨该方法没有在UI线程上运行(它打开带有登录对话框的浏览器)。

你有什么想法,怎么解决这个问题? 我是否需要编写自定义同步OidcClient

与其他类似的情况一样,您需要在没有太多重构的情况下将旧同步引入​​异步,我建议使用简单的“请稍候...”模式对话框。 该对话框启动异步操作,并在操作完成后自行关闭。

Window.ShowDialog是一种同步API,它阻止主UI,并且仅在模式对话框关闭时返回调用者。 但是,它仍然运行嵌套的消息循环并泵送消息。 因此,异步任务延续回调仍然被抽取和执行,而不是使用容易出现死锁的Task.Wait()

这是一个基本的,但完整的WPF例如,嘲讽了_oidcClient.LoginAsync()Task.Delay()和UI线程上执行它,请参阅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's 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();
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM