在 Task ContinueWith TaskScheduler.FromCurrentSynchronizationContext 的 ShowDialog 中打开表单时,应用程序会冻结

Posted

技术标签:

【中文标题】在 Task ContinueWith TaskScheduler.FromCurrentSynchronizationContext 的 ShowDialog 中打开表单时,应用程序会冻结【英文标题】:Application get freezes when opening Form in ShowDialog in Task ContinueWith TaskScheduler.FromCurrentSynchronizationContext 【发布时间】:2017-04-03 04:02:55 【问题描述】:

在我的 WPF 应用程序中,我使用 Task Parallelism 来增强多任务处理和 UI 响应能力,但我面临应用程序冻结的问题。

以下是我正在使用的代码:

MainForm.cs

Storyboard board = (Storyboard)this.FindResource("StoryboardLoadingAnimation");
board.Begin(this, true);
this.Cursor = Cursors.Wait;
Task.Factory.StartNew(() => 
     
        //Long Running Task 
    ).ContinueWith((prev) =>
    
        if (board != null)
        
            board.Stop(this);
        
        this.Cursor = Cursors.Arrow;

        this.popupOverlayHelper.Visibility = Visibility.Visible; // Adding overlay on main form by giving opacity and background color to popupOverlayHelper element              

        NotificationPopup notification = new NotificationPopup(this, NotificationsType.Info); // Another WPF Window form 
        notification.Owner = this;
        notification.ShowDialog();
        this.popupOverlayHelper.Visibility = Visibility.Collapsed;
    , TaskScheduler.FromCurrentSynchronizationContext());

NotificationPopup.xaml

<Window x:Class="Application.TrackOFF_NotificationPopup"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Application"
    mc:Ignorable="d" Title="NotificationPopup"
    WindowStyle="None" AllowsTransparency="True" ResizeMode="NoResize"
    WindowStartupLocation="CenterOwner"
    FontFamily="/TrackOFFApplication;component/Fonts/#Segoe UI"
    Width="1024px" Height="640px" Background="x:Null"
    MouseLeftButtonDown="NotificationPopup_MouseLeftButtonDown">
    <Grid Margin="240px,68px,0,0">
        <!--Other xaml code to show notification -->
    </Grid>
</Window>

NotificationPopup.xaml.cs

void NotificationPopup_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    try
    
        DragMove();
    
    catch (Exception ex)
    
        Helper.WriteLogEntries(ex, "NotificationPopup => NotificationPopup_MouseLeftButtonDown");
    

private void CloseMe(object sender, RoutedEventArgs e)

    try
    
        this.Close();
        if (IsParentToShowAgain)
            (ParentPopup as Window).Show();
        if (IsOverlayToKill)
        
            (ParentPopup as MainForm).popupOverlayHelper.Visibility = Visibility.Collapsed;
        
    
    catch (Exception ex)
    
        Helper.WriteLogEntries(ex, "NotificationPopup => CloseMe");
    

这个 NotificationPopup 没有任何复杂的功能,它只是向用户显示通知消息,然后单击右上角的“CloseMe”函数调用关闭弹出窗口并再次呈现 MainForm。

这里的问题是, 当通知弹出窗口保持打开超过几秒钟时,应用程序会冻结,或者我玩弹出窗口(比如将弹出屏幕从这里拖到那里)。

有趣的是,如果我从代码中删除任务,则应用程序不会冻结并按预期工作。 但我真的不想从代码中删除 Task,因为它有 Long Running 任务要执行,我想释放 UI 线程并向用户显示动画,直到 Long Running 任务完成他的工作。

您的宝贵意见将不胜感激。

谢谢!

【问题讨论】:

对于这种操作你应该使用Background Worker 也许你得先阅读this的文章。 【参考方案1】:

这里不需要使用Task.Factory.StartNewawait 您长时间运行的任务和您方法的其余部分将在捕获的上下文中运行。

我假设您的代码来自 Window_Loaded 或类似的事件处理程序:

private async void Window_Loaded(object sender, RoutedEventArgs e) 
    Storyboard board = (Storyboard)this.FindResource("StoryboardLoadingAnimation");
    board.Begin(this, true);
    this.Cursor = Cursors.Wait;

    //offload work from UI
    await Task.Run(() => 
        //Long Running Task 
    );

    //work is complete, execution returns to UI context
    if (board != null) 
        board.Stop(this);
    
    this.Cursor = Cursors.Arrow;

    this.popupOverlayHelper.Visibility = Visibility.Visible; // Adding overlay on main form by giving opacity and background color to popupOverlayHelper element              

    NotificationPopup notification = new NotificationPopup(this, NotificationsType.Info); // Another WPF Window form 
    notification.Owner = this;
    notification.ShowDialog();
    this.popupOverlayHelper.Visibility = Visibility.Collapsed;

【讨论】:

使用Task.Factory.StartNew处理大型计算任务有什么问题,然后在UI线程上使用ContinueWith处理响应和更新的UI元素。 是的,正确使用StarNew 时会出现许多问题,请参阅StartNew is Dangerous。当async/await 通常使代码变得更易于维护、更易于推理和更易于阅读时,几乎没有充分的理由使用基于延续的旧技术。

以上是关于在 Task ContinueWith TaskScheduler.FromCurrentSynchronizationContext 的 ShowDialog 中打开表单时,应用程序会冻结的主要内容,如果未能解决你的问题,请参考以下文章

组合ContinueWith

[C#]Task.ContinueWith使用实例

[C#]Task.ContinueWith使用实例

任务 - 如何确保 ContinueWith 操作是 STA 线程?

在 Task ContinueWith TaskScheduler.FromCurrentSynchronizationContext 的 ShowDialog 中打开表单时,应用程序会冻结

Task_等待任务完成几种方式