2

在过去的几天里,我一直在研究 DispatcherTimer,但我仍然无法理解一些东西。以下是我目前所了解的,

  1. tick 事件不会同时发生两次link
  2. 无需担心对象的所有者线程,因为调度程序计时器会自动执行 UI 线程中的所有工作
  3. 滴答的时间可能不是很准确,因为滴答本质上是从队列中执行的

现在我不清楚的是如果有另一个事件在滴答事件之间运行,则执行代码的顺序。我有一个测试 WPF 应用程序,它使用一个 DispatcherTimer,其滴答事件执行 2 个功能。firstStep()并按secondStep()顺序。

firstStep()变量设置为null同时secondStep()将其设置为非空值。设置值后, secondStep() 将启动一个包含 Completed 事件的故事板,该事件尝试访问此变量。

所以我的问题是,如果我们保持计时器运行,那么 Completed 事件是否可能介于firstStep()and函数之间?secondStep()我编写了一个测试应用程序,似乎就是这种情况,最终我们将达到一个状态,即当 Completed 事件被执行时变量为空。但是我不明白这是怎么发生的,因为firstStep()并按secondStep()顺序执行,所以在这两个函数之间应该不可能执行 Completed 事件(或者我在这里错了)。UI 线程是否并行执行 tick 和 Completed 事件?

有人可以详细解释一下 UI 线程是如何按顺序执行事件的,例如示例的故事板完成事件和 dispatcherTimer 的滴答声?感谢您的阅读,非常感谢您的评论,我正在努力解决这个问题。以下是我使用的测试代码,运行一段时间后最终会报错。

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

调用栈:

WpfApplication1.exe!WpfApplication1.Window1.storyBoardTest_Completed(object sender = {System.Windows.Media.Animation.ClockGroup}, System.EventArgs e = null) 第 63 行 C# PresentationCore.dll!System.Windows.Media.Animation.Clock.FireEvent (System.Windows.EventPrivateKey 键) + 0x5b 字节
PresentationCore.dll!System.Windows.Media.Animation.Clock.RaiseAccumulatedEvents() + 0x160 字节
PresentationCore.dll!System.Windows.Media.Animation.TimeManager.RaiseEnqueuedEvents() + 0x60字节
PresentationCore.dll!System.Windows.Media.Animation.TimeManager.Tick() + 0x28a 字节
PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandlerCore(object resizedCompositionTarget) + 0xbc 字节
PresentationCore.dll!System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(object resizedCompositionTarget) + 0x9d 字节

4

1 回答 1

2

每 500 毫秒,您将启动一个运行一秒钟的 Storyboard。这将不可避免地导致两个连续Completed事件而没有中间Tick事件。

因此,您必须检查是否windowTest已经null在您的Completed处理程序中:

void storyBoardTest_Completed(object sender, EventArgs e)
{
    if (windowTest != null)
    {
        windowTest.Title = "test";
        windowTest = null;
    }
}

即使 Storyboard 的运行时间少于 500 毫秒,也会有问题。由于Storyboard.Completed事件以与事件相同的方式附加到 Dispatcher 队列,DispatcherTimer.Tick并且 DispatcherTimer 和 Storyboard 的时间不准确,因此两个事件处理程序的执行顺序不可靠。因此,两个Completed事件可能在没有中间Tick事件的情况下发生。


您可以添加一些跟踪输出以查看两个处理程序在同一个线程中运行。

void dt_Tick(object sender, EventArgs e)
{
    Trace.TraceInformation("Tick: {0}", Thread.CurrentThread.ManagedThreadId);
    ...
}

void storyBoardTest_Completed(object sender, EventArgs e)
{
    Trace.TraceInformation("Completed: {0}", Thread.CurrentThread.ManagedThreadId);
    ...
} 
于 2013-02-16T12:49:59.607 回答