1

I have an issue with UI processes freezing in my application despite using a Task that should be created on a separate thread.

I have an image that I am rotating in a loading window that is being shown/hidden as needed, here is the 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>

I also have an event handler in my code-behind for the window that is restarting the animation when the window's visibility is changed:

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

I have a controller that is responsible for showing/hiding/changing the message in the loading window, which has methods as follows:

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

In my App.xaml.cs file I have code similar to the following to start my application's loading process:

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

I use TaskCreationOptions.LongRunning to ensure that I actually get a new thread.

Because the code in PerformNotSoIntensiveLoadingOperation() doesn't take very long to run (on my PC anyway), the loading animation works fine.

When I hide the window and a more intensive operation is run in PerformIntensiveLoadingOperations() then I show the window again, the animation freezes.

If I leave the window open after the intensive operation is complete, the animation continues as I would expect.

For clarification, the process is as follows:

Show loading screen > do some work > hide loading screen > show another window > wait for user interaction before closing that window > show the loading window again > do some more work > hide loading window again > show main application window

It's during the do some more work part on the Task's thread (which doesn't touch any UI code) that my animation is freezing.

I am using .Net 4.0.3.

If I remove my calls to Application.Current.Dispatcher.Invoke I get the expected cross-thread violation exception telling me that the calling thread does not own the object I am trying to access, so my code is definitely not being run on the UI thread.

What is causing this and how can I prevent it from happening?

Sean Airey
  • 6,352
  • 1
  • 20
  • 38

1 Answers1

3

In your Task, you are trying to do UI work... I don't believe that you can do that. You can't access UI controls from a background thread. You may find that it is the UI thread that is busy rendering or notifying and making the busy indicator freeze.

To be clear, all UI work is accomplished on the UI thread. So if you try to display something in the UI from a background thread, either you'll get the standard error, or the code will automatically marshal itself onto the UI thread.

I have a similar setup in a large scale WPF application and it suffers from the same problem. The busy indicator displays fine while data is being fetched from the database, but then when the application starts to render the data in the UI, the busy indicator freezes.

I even tried using an animated Gif to get around this issue, but of course they don't animate (by themselves) in WPF applications. Having written the code to animate the frames in the Gif, I was very disappointed to find that it also suffered from the same problem.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • The only UI work being accomplished in my situation is the showing/hiding of the window. After that, all the code deals with making calls to web services and database work. Also, I explicitly use the Dispatcher to call the show/hide methods (as in my code examples) as without it (as expected) I was getting the "Calling thread does not own this object" error. The loading window is very simple, it has a `TextBlock` and an `Image` for which the full code is shown. The other windows are shown after the loading window is hidden, by which time I don't care if it's not animating. – Sean Airey Jul 17 '14 at 13:19
  • 2
    'but then when the application starts to render the data in the UI, the busy indicator freezes.' the UI thread can't animate the busy indicator whilst it is rendering the rest of the UI... – AwkwardCoder Jul 17 '14 at 13:22
  • @AwkwardCoder By which time, the loading window is hidden and I don't mind that it isn't being animated because it is hidden anyway. What is happening is that I am showing a loading screen, doing some work, hiding the loading screen, showing another window, waiting for user interaction, closing that window, showing the loading window, doing some more work, hiding it again and showing the main application window. It's during the "doing some work" and "doing some more work" on the Task's thread (which doesn't touch any UI code) that my animation is freezing. – Sean Airey Jul 17 '14 at 13:25
  • which version of .Net is this? – AwkwardCoder Jul 17 '14 at 13:29
  • @AwkwardCoder 4.0. I'll include that in my question – Sean Airey Jul 17 '14 at 13:31
  • @AwkwardCoder Sorry, 4.0.3 as I use LocalDb – Sean Airey Jul 17 '14 at 13:33
  • have you confirmed the Task is NOT being created on the UI thread, I know you've specified TaskCreationOptions.LongRunning but I'm not convinced this will always do what you expect. – AwkwardCoder Jul 17 '14 at 13:35
  • @AwkwardCoder If I remove my calls to `Dispatcher.Invoke` I get the usual, expected, exception telling me that the calling thread does not own the object I'm trying to access. – Sean Airey Jul 17 '14 at 13:38
  • So I think that the work is being done on the UI thread because the application locks up in places I wouldn't expect it to if it wasn't being done on the UI thread if that makes any sense. So I've accepted this because of "or the code will automatically marshal itself onto the UI thread." – Sean Airey Aug 15 '14 at 13:15