代码之家  ›  专栏  ›  技术社区  ›  Alex34758

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

  •  2
  • Alex34758  · 技术社区  · 7 年前

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

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

    2) 如果我通过手动单击红十字来关闭它,系统将关闭。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
            }
        }
    }
    
    5 回复  |  直到 6 年前
        1
  •  3
  •   Peter Bons    7 年前

    这里不需要使用延续符,只需坚持 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();
        }
    }
    

    出现错误的原因是(继续)由创建的任务 任务跑 在非UI线程上执行,不允许访问UI( w.Close(); )在非UI线程中。

    如果你有从任务中受益的工作。运行您可以修改 Go() 方法如下:

    async Task Go(Window w)
    {
        await Task.Run(() => { 
            // ... heavy work here  
        });    
        w.Close();
    }
    
        2
  •  0
  •   Alex34758    7 年前

    我阅读了Peter Bons的答案并重新思考了它——他的方法使用了异步Go方法(当然,这在我的问题中也是如此),然后等待结果而不执行任务。跑我得出的另一个变体是基于Peter Bons和Andrew Shkolik的答案。我调用一个同步方法来异步完成任务。运行并使用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
  •   Theodor Zoulias    5 年前

    下面是一个扩展方法 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
  •   Andrew Shkolik    7 年前

    您正在尝试从后台线程关闭窗口。。。 如果仍要使用任务。运行()。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;
        }
    
        5
  •  -1
  •   solice    5 年前

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