如何阻止我的 UI 线程锁定?

Posted

技术标签:

【中文标题】如何阻止我的 UI 线程锁定?【英文标题】:How do I stop my UI thread from locking up? 【发布时间】:2014-07-17 12:51:26 【问题描述】:

尽管使用了应在单独线程上创建的任务,但我遇到了 UI 进程在我的应用程序中冻结的问题。

我有一个正在加载窗口中旋转的图像,它会根据需要显示/隐藏,这里是 XAML:

<Image Name="LoadingCog" Source="/Resources/Cog.png" Margin="0,37" Width="60" Height="60" HorizontalAlignment="Center" RenderTransformOrigin="0.5,0.5">
    <Image.Resources>
        <Storyboard x:Key="LoadingCogRotateStoryboard">
            <DoubleAnimation
                    Storyboard.TargetName="LoadingCogRotateTransform"
                    Storyboard.TargetProperty="Angle"
                    By="10"
                    To="360"
                    Duration="0:0:7"
                    FillBehavior="Stop"
                    RepeatBehavior="Forever" />
        </Storyboard>
    </Image.Resources>
    <Image.RenderTransform>
        <RotateTransform x:Name="LoadingCogRotateTransform" Angle="0" />
    </Image.RenderTransform>
</Image>

我的代码隐藏中还有一个事件处理程序,用于在窗口可见性更改时重新启动动画的窗口:

void LoadingScreenWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)

    var storyBoard = (System.Windows.Media.Animation.Storyboard)LoadingCog.Resources["LoadingCogRotateStoryboard"];

    if (IsVisible == true)
    
        storyBoard.Seek(TimeSpan.Zero);
        storyBoard.Begin();
    
    else
    
        storyBoard.Stop();
    

我有一个控制器,负责在加载窗口中显示/隐藏/更改消息,其方法如下:

internal void ShowLoadingScreen(string loadingMessage)

    Application.Current.Dispatcher.Invoke((Action)(() =>
    
        _viewModel.LoadingMessage = loadingMessage;

        _loadingWindow.Show();
    ));


internal void HideLoadingScreen()

    Application.Current.Dispatcher.Invoke((Action)(() =>
    
        _loadingWindow.Hide();
    ));

在我的App.xaml.cs 文件中,我有类似于以下的代码来启动我的应用程序的加载过程:

Task.Factory.StartNew(() =>

    LoadingScreenController.ShowLoadingScreen("Loading application, please wait..");

    PerformNotSoIntensiveLoadingOperation();

    HideLoadingScreen();

    System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
    
        LoginWindow.Show();
    ));

    LoadingScreenController.ShowLoadingScreen("Logging in, please wait..");

    PerformIntensiveLoadingOperations();

    LoadingScreenController.HideLoadingScreen();

    System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
    
        MainWindow.Show();
    ));
, TaskCreationOptions.LongRunning);

我使用TaskCreationOptions.LongRunning 来确保我确实获得了一个新线程。

因为PerformNotSoIntensiveLoadingOperation() 中的代码运行时间不会很长(无论如何在我的电脑上),所以加载动画效果很好。

当我隐藏窗口并在PerformIntensiveLoadingOperations() 中运行更密集的操作然后我再次显示窗口时,动画会冻结。

如果我在密集操作完成后让窗口保持打开状态,动画会按预期继续。

为了澄清,流程如下:

显示加载屏幕 > 做一些工作 > 隐藏加载屏幕 > 显示另一个窗口 > 在关闭该窗口之前等待用户交互 > 再次显示加载窗口 > 做更多工作 > 再次隐藏加载窗口 > 显示主应用程序窗口

Task 的线程(不涉及任何UI 代码)的do some more work 部分,我的动画冻结了。

我正在使用 .Net 4.0.3。

如果我删除对Application.Current.Dispatcher.Invoke 的调用,我会收到预期的跨线程冲突异常,告诉我调用线程不拥有我要访问的对象,因此我的代码肯定不会在 UI 线程上运行.

这是什么原因造成的,我该如何防止它发生?

【问题讨论】:

【参考方案1】:

在您的Task 中,您正在尝试进行 UI 工作...我不相信您可以做到这一点。您无法从后台线程访问 UI 控件。您可能会发现是 UI 线程忙于渲染或通知并使忙指示器冻结。

明确地说,所有 UI 工作都在 UI 线程上完成。因此,如果您尝试从后台线程在 UI 中显示某些内容,您会得到标准错误,或者代码会自动将自身编组到 UI 线程。

我在大型 WPF 应用程序中有类似的设置,它也遇到了同样的问题。在从数据库中获取数据时,忙碌指示器显示正常,但是当应用程序开始在 UI 中呈现数据时,忙碌指示器会冻结。

我什至尝试使用动画 Gif 来解决这个问题,但当然它们不会在 WPF 应用程序中(自己)制作动画。编写了代码来为 Gif 中的帧设置动画后,我很失望地发现它也遇到了同样的问题。

【讨论】:

在我的情况下,唯一完成的 UI 工作是显示/隐藏窗口。之后,所有代码都处理调用 Web 服务和数据库工作。此外,我明确使用 Dispatcher 调用显示/隐藏方法(如在我的代码示例中),因为没有它(如预期的那样)我收到“调用线程不拥有此对象”错误。加载窗口非常简单,它有一个TextBlock 和一个Image,其中显示了完整的代码。隐藏加载窗口后会显示其他窗口,到那时我不在乎它是否没有动画。 '但是当应用程序开始在 UI 中呈现数据时,忙碌指示器会冻结。' UI 线程在渲染 UI 的其余部分时无法为繁忙指示器设置动画... @AwkwardCoder 到那时,加载窗口被隐藏了,我不介意它没有动画,因为它无论如何都被隐藏了。发生的事情是我正在显示加载屏幕,做一些工作,隐藏加载屏幕,显示另一个窗口,等待用户交互,关闭该窗口,显示加载窗口,做更多工作,再次隐藏它并显示主应用程序窗口。在任务线程(不涉及任何 UI 代码)上“做一些工作”和“做更多工作”期间,我的动画冻结了。 这是哪个版本的 .Net? @AwkwardCoder 4.0。我将把它包括在我的问题中

以上是关于如何阻止我的 UI 线程锁定?的主要内容,如果未能解决你的问题,请参考以下文章

C# 线程 - 锁 - 如何检查锁的出现

锁定多个线程

在互斥锁解锁时取消阻止定时接收

如果我需要在锁定互斥锁后解锁它,我该如何返回一个值?

多个线程试图锁定

仅当未附加性能分析器时 UI 线程锁定