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

时间:2018-01-20 11:37:27

标签: c# wpf modal-dialog async-await task

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

1)工作完成后("完成!"消息框显示,但它也不重要,只是指示),ProgressWindow不会自动关闭;

2)如果我通过手动点击红叉来关闭它,System.InvalidOperationException带有消息"调用线程无法访问此对象,因为另一个线程拥有它。"发生在

await task;

3)Continue方法中的代码实际上是在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
        }
    }
}

5 个答案:

答案 0 :(得分:1)

此处无需使用延续,只需坚持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(w.Close();)非UI线程。

如果您的工作受益于Task.Run,​​您可以像这样修改Go()方法:

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

答案 1 :(得分:0)

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

    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;
    }

答案 2 :(得分: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();
            }));
    }
}

答案 3 :(得分:0)

这是扩展方法ShowDialogUntilTaskCompletion,可以代替内置ShowDialog使用。提供的Task完成后,它将自动关闭窗口。如果任务异常完成,则窗口仍将关闭,并且该方法返回时不会抛出异常。

如果通过其他方式(例如,用户单击窗口的关闭按钮或按Alt + F4)关闭了窗口,则此方法也会返回。因此,不能保证该方法返回时任务会完成。

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));

        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
                {
                    await closeDelay;
                    window.Close();
                }
                catch { } // Ignore exception
            }
        }
    }

}

作为奖励,它还接受一个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
    }
}

答案 4 :(得分:-1)

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