任务完成后如何关闭模态窗口?

问题描述 投票:0回答:5

我有两个默认窗口。 我希望一个窗口开始一项工作,以模式(对话框)形式显示另一个窗口(指示进度,但现在并不重要),然后在这项工作完成后关闭它。我在实现中遇到以下问题:

1)工作完成后(出现“Completed!”消息框,但这也不重要,只是指示),ProgressWindow不会自动关闭;

2)如果我通过手动单击红叉将其关闭,则会出现 System.InvalidOperationException 并显示消息“调用线程无法访问此对象,因为另一个线程拥有它。”发生在线上

await task;

3)ContinueWith 中的代码实际上是在 Go 方法完成之前执行的 - 为什么?

我怎样才能实现这样的行为?

我的实现:

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

        async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Window w = new ProgressWindow();

            var task = 
                Task
                .Run(() => Go())
                .ContinueWith(completedTask => w.Close());
            w.ShowDialog();
            await task; // InvalidOperationException throws
        }

        async protected void Go()
        {
            await Task.Delay(500); // imitate some work    
            MessageBox.Show("Completed!"); // indicate that work has been completed
        }
    }
}
c# wpf modal-dialog async-await task
5个回答
3
投票

这里不需要使用延续,只需坚持

await
。不仅如此,因为您使用了
async void
,程序没有等待半秒就关闭了窗口。

此外,在这种情况下使用

Task.Run
实际上没有任何好处,因为
Go
已经可以等待。

改进简化后的代码为:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Window w = new ProgressWindow();
        Task work = Go(w);
        w.ShowDialog();
        await work; // exceptions in unawaited task are difficult to handle, so let us await it here.
    }

    async Task Go(Window w)
    {
        await Task.Delay(500);    
        w.Close();
    }
}

您收到错误的原因是

Task.Run
创建的任务(的延续)在非UI线程上执行,并且不允许在非UI线程中访问UI(
w.Close();
)。

如果您的工作受益于 Task.Run,您可以像这样修改

Go()
方法:

async Task Go(Window w)
{
    await Task.Run(() => { 
        // ... heavy work here  
    });    
    w.Close();
}

1
投票

这里有一个扩展方法

ShowDialogUntilTaskCompletion
,可以用来代替内置的
ShowDialog
。当提供的
Task
完成时,它会自动关闭窗口。如果任务完成时出现异常,窗口仍然关闭,并且该方法返回而不抛出异常。

可以通过处理窗口上的

Closing
事件并将
CancelEventArgs.Cancel
属性设置为 true 来阻止用户关闭窗口(并导致方法在任务完成之前返回)。该方法在调用
Window.Close()
方法之前取消订阅事件,否则将被取消。

public static class WindowExtensions
{
    public static bool? ShowDialogUntilTaskCompletion(this Window window,
        Task task, int minDurationMsec = 500)
    {
        if (window == null) throw new ArgumentNullException(nameof(window));
        if (task == null) throw new ArgumentNullException(nameof(task));
        if (minDurationMsec < 0)
            throw new ArgumentOutOfRangeException(nameof(minDurationMsec));

        window.Closing += PreventUserClosing;
        var closeDelay = Task.Delay(minDurationMsec);
        HandleTaskCompletion();
        return window.ShowDialog();

        async void HandleTaskCompletion()
        {
            try
            {
                await Task.Yield(); // Ensure that the completion is asynchronous
                await task;
            }
            catch { } // Ignore exception
            finally
            {
                try
                {
                    window.Closing -= PreventUserClosing;
                    await closeDelay;
                    window.Close();
                }
                catch { } // Ignore exception
            }
        }

        void PreventUserClosing(object sender, CancelEventArgs e)
        {
            e.Cancel = true;
        }
    }

}

作为奖励,它还接受

minDurationMsec
参数,以使窗口在指定的最短持续时间内保持可见(以便窗口不会在眨眼间关闭)。

使用示例:

async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Window w = new ProgressWindow();
    var task = Task.Delay(2000); // Simulate some asynchronous work
    w.ShowDialogUntilTaskCompletion(task);
    try
    {
        // Most likely the task will be completed at this point
        await task;
    }
    catch (Exception ex)
    {
        // Handle the case of a faulted task
    }
}

0
投票

我阅读了 Peter Bons 的答案并重新思考它 - 他的方法使用异步 Go 方法(当然就像我的问题中一样),然后等待其结果而不使用 Task.Run。我得出的另一个变体是基于 Peter Bons 和 Andrew Shkolik 的答案。我通过Task.Run异步调用同步方法Go,并使用Dispatcher来操作窗口。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Window w = new ProgressWindow();
        Task work = Task.Run(() => Go(w));
        w.ShowDialog();
        await work;
    }

    void Go(Window w)
    {
        Thread.Sleep(2000); // imitate some work
        Dispatcher.BeginInvoke(
            new Action(() =>
            {
                w.Close();
            }));
    }
}

-1
投票

您试图从后台线程关闭窗口... 如果您仍然想使用 Task.Run().ContinueWith() 那么您应该使用 Dispatcher 来关闭窗口。但最好使用异步等待语法。

    async void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        Window w = new ProgressWindow();

        var task = Task.Run(() => Go()).ContinueWith(completedTask =>
            {
                Application.Current.Dispatcher.BeginInvoke(
                DispatcherPriority.Send, new Action(() =>
                {
                    w.Close(); 
                }));

            });
        w.ShowDialog();
        await task;
    }

-1
投票

async (p) => 
      { 
      await Task.Run(() => { 
      
      if (p == null) return; 
      //code
      }).ConfigureAwait(true); 
      p.Close(); 
      });

© www.soinside.com 2019 - 2024. All rights reserved.