I've spent the last few days looking at the DispatcherTimer and I still can't wrap my head around some stuff. Here's what I understand so far,
- the tick event will not occur twice at the same time link
- there is no need to worry about the owner thread(s) of the objects since the dispatcher timer automatically performs all the work in the UI thread
- the timing of the ticks may not be very accurate since the ticks are essentially executed from a queue
Now what I'm not clear about is the order of the code being executed if there is another event which runs in between a tick event. I've a test WPF application which uses a DispatcherTimer whose tick event performs 2 functions. firstStep()
and secondStep()
in sequence.
The firstStep()
sets a variable to null
while secondStep()
sets it to a value that is not null. After setting the value, secondStep() will begin a storyboard which has a Completed event, which attempts to access this variable.
So my question is, is it possible for the Completed event to come in between the firstStep()
and secondStep()
function if we keep the timer running? I've written a test application and it seems to be that case, eventually we will reach a state where the variable is null when the Completed event gets executed. But I don't understand how that can happen, since firstStep()
and secondStep()
get executed in sequence, there should be no way the Completed event can be executed between the 2 functions (or I am wrong here). Does the UI thread execute the tick and the Completed event in parallel?
Can someone explain to me in detail how the UI thread executes events such as the example's storyboard completed event and dispatcherTimer's ticks in sequence? Thanks for reading, your comments are very much appreciated I'm trying very hard to get my head around this. The following is the test code I used, it will eventually throw an error after running for a while.
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
storyBoardTest = new Storyboard();
storyBoardTest.Completed += new EventHandler(storyBoardTest_Completed);
DoubleAnimation animation = new DoubleAnimation(1, 0.9, new Duration(TimeSpan.FromSeconds(1)));
Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new PropertyPath(UIElement.OpacityProperty));
storyBoardTest.Children.Add(animation);
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(500);
dt.Tick += new EventHandler(dt_Tick);
dt.Start();
}
private Window windowTest = null;
private Storyboard storyBoardTest = null;
void dt_Tick(object sender, EventArgs e)
{
firstStep();
secondStep();
}
private void firstStep()
{
windowTest = null;
}
private void secondStep()
{
windowTest = this;
storyBoardTest.Stop();
storyBoardTest.Begin(this);
}
void storyBoardTest_Completed(object sender, EventArgs e)
{
//Attempt to access object throws null error. Why?
windowTest.Title = "test";
windowTest = null;
}
}
CallStack:
WpfApplication1.exe!WpfApplication1.Window1.storyBoardTest_Completed(object sender = {System.Windows.Media.Animation.ClockGroup}, System.EventArgs e = null) Line 63 C#
PresentationCore.dll!System.Windows.Media.Animation.Clock.FireEvent(System.Windows.EventPrivateKey key) + 0x5b bytes
PresentationCore.dll!System.Windows.Media.Animation.Clock.RaiseAccumulatedEvents() + 0x160 bytes
PresentationCore.dll!System.Windows.Media.Animation.TimeManager.RaiseEnqueuedEvents() + 0x60 bytes
PresentationCore.dll!System.Windows.Media.Animation.TimeManager.Tick() + 0x28a bytes
PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandlerCore(object resizedCompositionTarget) + 0xbc bytes
PresentationCore.dll!System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(object resizedCompositionTarget) + 0x9d bytes