Reputation: 6372
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?
Upvotes: 1
Views: 2233
Reputation: 69959
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.
Upvotes: 3