在后台操作中间显示一个模态 UI 并继续 [英] Show a modal UI in the middle of background operation and continue

查看:24
本文介绍了在后台操作中间显示一个模态 UI 并继续的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 WPF 应用程序运行一个使用 async/await 的后台任务.任务是随着应用程序的进展更新应用程序的状态 UI.在这个过程中,如果满足了某个条件,我需要展示一个模态窗口让用户知道这样的事件,然后继续处理,现在也更新那个模态窗口的状态UI.

I have a WPF application running a background task which uses async/await. The task is updating the app's status UI as it progresses. During the process, if a certain condition has been met, I am required to show a modal window to make the user aware of such event, and then continue processing, now also updating the status UI of that modal window.

这是我试图实现的草图版本:

This is a sketch version of what I am trying to achieve:

async Task AsyncWork(int n, CancellationToken token)
{
    // prepare the modal UI window
    var modalUI = new Window();
    modalUI.Width = 300; modalUI.Height = 200;
    modalUI.Content = new TextBox();

    using (var client = new HttpClient())
    {
        // main loop
        for (var i = 0; i < n; i++)
        {
            token.ThrowIfCancellationRequested();

            // do the next step of async process
            var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);

            // update the main window status
            var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
            ((TextBox)this.Content).AppendText(info); 

            // show the modal UI if the data size is more than 42000 bytes (for example)
            if (data.Length < 42000)
            {
                if (!modalUI.IsVisible)
                {
                    // show the modal UI window 
                    modalUI.ShowDialog();
                    // I want to continue while the modal UI is still visible
                }
            }

            // update modal window status, if visible
            if (modalUI.IsVisible)
                ((TextBox)modalUI.Content).AppendText(info);
        }
    }
}

modalUI.ShowDialog() 的问题在于它是一个阻塞调用,因此处理停止,直到对话框关闭.如果窗口是非模态的,那不会有问题,但它必须是模态的,如项目要求所规定的那样.

The problem with modalUI.ShowDialog() is that it is a blocking call, so the processing stops until the dialog is closed. It would not be a problem if the window was modeless, but it has to be modal, as dictated by the project requirements.

有没有办法用 async/await 解决这个问题?

Is there a way to get around this with async/await?

推荐答案

这可以通过异步执行 modalUI.ShowDialog() 来实现(在 UI 线程的消息循环的未来迭代中).ShowDialogAsync 的以下实现通过使用 TaskCompletionSource (EAP 任务模式)和SynchronizationContext.Post.

This can be achieved by executing modalUI.ShowDialog() asynchronously (upon a future iteration of the UI thread's message loop). The following implementation of ShowDialogAsync does that by using TaskCompletionSource (EAP task pattern) and SynchronizationContext.Post.

这样的执行工作流可能有点难以理解,因为您的异步任务现在分布在两个独立的 WPF 消息循环中:主线程的一个和新的嵌套的一个(由 ShowDialog 开始).IMO,这完全没问题,我们只是利用了 C# 编译器提供的 async/await 状态机.

Such execution workflow might be a bit tricky to understand, because your asynchronous task is now spread across two separate WPF message loops: the main thread's one and the new nested one (started by ShowDialog). IMO, that's perfectly fine, we're just taking advantage of the async/await state machine provided by C# compiler.

虽然,当您的任务结束而模态窗口仍处于打开状态时,您可能希望等待用户关闭它.这就是 CloseDialogAsync 在下面所做的.此外,您可能应该考虑用户在任务中间关闭对话框的情况(AFAIK,WPF 窗口不能重复用于多个 ShowDialog 调用).

Although, when your task comes to the end while the modal window is still open, you probably want to wait for user to close it. That's what CloseDialogAsync does below. Also, you probably should account for the case when user closes the dialog in the middle of the task (AFAIK, a WPF window can't be reused for multiple ShowDialog calls).

以下代码对我有用:

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

namespace WpfAsyncApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.Content = new TextBox();
            this.Loaded += MainWindow_Loaded;
        }

        // AsyncWork
        async Task AsyncWork(int n, CancellationToken token)
        {
            // prepare the modal UI window
            var modalUI = new Window();
            modalUI.Width = 300; modalUI.Height = 200;
            modalUI.Content = new TextBox();

            try
            {
                using (var client = new HttpClient())
                {
                    // main loop
                    for (var i = 0; i < n; i++)
                    {
                        token.ThrowIfCancellationRequested();

                        // do the next step of async process
                        var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);

                        // update the main window status
                        var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
                        ((TextBox)this.Content).AppendText(info);

                        // show the modal UI if the data size is more than 42000 bytes (for example)
                        if (data.Length < 42000)
                        {
                            if (!modalUI.IsVisible)
                            {
                                // show the modal UI window asynchronously
                                await ShowDialogAsync(modalUI, token);
                                // continue while the modal UI is still visible
                            }
                        }

                        // update modal window status, if visible
                        if (modalUI.IsVisible)
                            ((TextBox)modalUI.Content).AppendText(info);
                    }
                }

                // wait for the user to close the dialog (if open)
                if (modalUI.IsVisible)
                    await CloseDialogAsync(modalUI, token);
            }
            finally
            {
                // always close the window
                modalUI.Close();
            }
        }

        // show a modal dialog asynchronously
        static async Task ShowDialogAsync(Window window, CancellationToken token)
        {
            var tcs = new TaskCompletionSource<bool>();
            using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
            {
                RoutedEventHandler loadedHandler = (s, e) =>
                    tcs.TrySetResult(true);

                window.Loaded += loadedHandler;
                try
                {
                    // show the dialog asynchronously 
                    // (presumably on the next iteration of the message loop)
                    SynchronizationContext.Current.Post((_) => 
                        window.ShowDialog(), null);
                    await tcs.Task;
                    Debug.Print("after await tcs.Task");
                }
                finally
                {
                    window.Loaded -= loadedHandler;
                }
            }
        }

        // async wait for a dialog to get closed
        static async Task CloseDialogAsync(Window window, CancellationToken token)
        {
            var tcs = new TaskCompletionSource<bool>();
            using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
            {
                EventHandler closedHandler = (s, e) =>
                    tcs.TrySetResult(true);

                window.Closed += closedHandler;
                try
                {
                    await tcs.Task;
                }
                finally
                {
                    window.Closed -= closedHandler;
                }
            }
        }

        // main window load event handler
        async void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var cts = new CancellationTokenSource(30000);
            try
            {
                // test AsyncWork
                await AsyncWork(10, cts.Token);
                MessageBox.Show("Success!");
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }
}

下面是一种稍微不同的方法,它使用 Task.Factory.StartNew 异步调用 modalUI.ShowDialog().返回的 Task 可以稍后等待,以确保用户已关闭模态对话框.

Below is a slightly different approach which uses Task.Factory.StartNew to invoke modalUI.ShowDialog() asynchronously. The returned Task can be awaited later to make sure the user has closed the modal dialog.

async Task AsyncWork(int n, CancellationToken token)
{
    // prepare the modal UI window
    var modalUI = new Window();
    modalUI.Width = 300; modalUI.Height = 200;
    modalUI.Content = new TextBox();

    Task modalUITask = null;

    try
    {
        using (var client = new HttpClient())
        {
            // main loop
            for (var i = 0; i < n; i++)
            {
                token.ThrowIfCancellationRequested();

                // do the next step of async process
                var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);

                // update the main window status
                var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
                ((TextBox)this.Content).AppendText(info);

                // show the modal UI if the data size is more than 42000 bytes (for example)
                if (data.Length < 42000)
                {
                    if (modalUITask == null)
                    {
                        // invoke modalUI.ShowDialog() asynchronously
                        modalUITask = Task.Factory.StartNew(
                            () => modalUI.ShowDialog(),
                            token,
                            TaskCreationOptions.None,
                            TaskScheduler.FromCurrentSynchronizationContext());

                        // continue after modalUI.Loaded event 
                        var modalUIReadyTcs = new TaskCompletionSource<bool>();
                        using (token.Register(() => 
                            modalUIReadyTcs.TrySetCanceled(), useSynchronizationContext: true))
                        {
                            modalUI.Loaded += (s, e) =>
                                modalUIReadyTcs.TrySetResult(true);
                            await modalUIReadyTcs.Task;
                        }
                    }
                }

                // update modal window status, if visible
                if (modalUI.IsVisible)
                    ((TextBox)modalUI.Content).AppendText(info);
            }
        }

        // wait for the user to close the dialog (if open)
        if (modalUITask != null)
            await modalUITask;
    }
    finally
    {
        // always close the window
        modalUI.Close();
    }
}

这篇关于在后台操作中间显示一个模态 UI 并继续的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆