Race condition when using a Binding on a DoubleAnimation inside of a Storyboard
Nice example of a race condition on MSDN Forums, because the Binding is running on a different thread and won’t update in time. Let’s look at the problem:
There’s this Storyboard in the MainWindow to animate the Rectangle with the name “rectangle”:
<Window.Resources>
<local:DurationConverter x:Key="DurationConverter"></local:DurationConverter>
<Storyboard x:Key="storyboard">
<DoubleAnimation Storyboard.TargetProperty="Width" Storyboard.TargetName="rectangle" To="500"
Duration="{Binding Converter={StaticResource DurationConverter}}">
</DoubleAnimation>
</Storyboard>
</Window.Resources>
<Grid Background="Black">
<Rectangle x:Name="rectangle" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="White"
MouseDown="rectangle_MouseDown"/>
</Grid>
In the codebehind-File the Storyboard is started in the rectangle_MouseDown-handler by calling the Begin-method on it:
private void rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
Storyboard up = (Storyboard)this.Resources["storyboard"];
up.Begin();
}
The DurationConverter returns a Duration of one Minute:
class DurationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new System.Windows.Duration(TimeSpan.FromMinutes(1));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
When you click on the Rectangle, you’ll see that it is animated with the default duration of 1 second instead of using the duration of 1 minute. When you add breakpoint in the Convert-method of the DurationConverter, you’ll see that the Convert-method is called after the call to the Begin-method of the Storyboard. So it’s too late to grab the wanted Duration.
How can you solve it? Just use a little trick to delay the call of the Begin-method on the Storyboard by one millisecond. This will do it:
private async void rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
Storyboard up = (Storyboard)this.Resources["storyboard"];
await Task.Delay(1);
up.Begin();
}
Comments (4)
I checked, it works.
Another option may be to subscribe to TargetUpdated event of Duration to begin Storyboard.
Yes, that could work as well. But I haven’t tried it.
Is it possible to “debug” a race condition using a debugger? Should one use logging to “debug” a race?
Hi Jorge,
it depends. :) If you own the code or if you have the debugging symbols, then you can set a breakpoint in the code and work with the Threading-Window to switch between threads and to find it out. Logging works as well.
Thomas