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

时间:2014-07-17 12:51:27

标签: c# wpf multithreading animation

尽管使用了应该在单独的线程上创建的Task,但我的应用程序中的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()中运行更密集的操作时,我再次显示窗口,动画冻结。

如果我在密集操作完成后让窗口保持打开状态,动画将继续如我所料。

澄清一下,过程如下:

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

do some more work主题的Task部分{我没有触及任何用户界面的代码},我的动画正在冻结。

我正在使用.Net 4.0.3。

如果我删除了对Application.Current.Dispatcher.Invoke的调用,我得到了预期的跨线程冲突异常,告诉我调用线程不拥有我试图访问的对象,所以我的代码肯定没有运行在UI线程。

造成这种情况的原因是什么?如何防止它发生?

1 个答案:

答案 0 :(得分:2)

在你的Task中,你正在努力做UI工作......我不相信你能做到这一点。您无法从后台线程访问UI控件。您可能会发现正在忙于呈现或通知并使忙指示符冻结的UI线程。

要明确,所有UI工作都是在UI线程上完成的。因此,如果您尝试在后台线程中在UI中显示某些内容,则您将获得标准错误,或者代码将自动封送到UI线程中。

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

我甚至尝试使用动画Gif来解决这个问题,但当然他们在WPF应用程序中没有动画(自己)。编写代码来为Gif中的帧设置动画后,我发现它也遇到了同样的问题,我感到非常失望。