在 ThreadPool.QueueUserWorkItem() 方法中启动时 DispatcherTimer 滴答不工作

DispatcherTimer ticking not working when started in ThreadPool.QueueUserWorkItem() method

我已经为我的 WPF 应用程序创建了 TimerManager class。 class 处理调度程序计时器的启动和停止。 这是 class:

public static class TimerManager
{
    static DispatcherTimer disTimer;
    static Model m = Model.GetInstance();
    static TimerManager()
    {
        disTimer = new DispatcherTimer();
        disTimer.Tick += disTimer_tick;
        disTimer.Interval = new TimeSpan(0, 0, 1);
    }

    public static void StartTimer()
    {
        disTimer.Start();

    }

    public static void StopTimer()
    {
        disTimer.Stop();
    }

    private static void disTimer_tick(object sender, EventArgs e)
    {
        m.Tick++;
    }
}

我创建了一个 Model class 代表 UI 中的滴答声。 (在 MainWindow.xaml 中绑定 -> xy 文本框文本字段 "{Binding Tick}")。

class Model : INotifyPropertyChanged
{
    private Model()
    {

    }

    static Model instance;
    public static Model GetInstance()
    {
        if (instance == null)
        {
            instance = new Model();
        }
        return instance;
    }

    int tick;

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnNotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public int Tick
    {
        get
        {
            return tick;
        }

        set
        {
            tick = value;
            OnNotifyPropertyChanged();
        }
    }
}

这里是 MainWindow class:

Model m;
public MainWindow()
{
    InitializeComponent();
    m = Model.GetInstance();
    this.DataContext = m;
}

private void startButton_Click(object sender, RoutedEventArgs e)
{
    ThreadPool.QueueUserWorkItem(o =>
    {
        TimerManager.StartTimer();
    });
    //TimerManager.StartTimer();
}

private void stopButton_Click(object sender, RoutedEventArgs e)
{
    TimerManager.StopTimer();
}

当我点击开始按钮时,我使用了 ThreadPool.QueueUserWorkItem() 方法。在那种方法中,我启动了计时器,但计时器滴答声不是每一秒 运行。

当我不使用 ThreadPool 时,这有效。但是这个解决方案对我不利; ThreadPool 对我来说很重要,因为我使用 HTTP Web 服务器(在本地)。

我的问题是:为什么我使用 ThreadPool 时滴答不工作?

DispatcherTimer 对象具有线程关联。也就是说,它绑定到一个特定的线程。特别是,它专门设计用于在创建它的线程中引发其 Tick 事件,使用该线程的 Dispatcher

您的 ThreadManager class 的静态构造函数将在首次使用该类型时被调用。在您的非工作示例中,这发生在排队的工作项方法中,导致静态构造函数在用于执行该工作项方法的线程池线程中执行。这反过来会导致您创建的 DispatcherTimer 对象由该线程拥有,并由该线程的 Dispatcher 在该线程中引发其 Tick 事件。

除此之外,线程池线程没有 Dispatcher。所以没有 Dispatcher 可以引发 DispatcherTimer 对象的 Tick 事件。即使有,如果不调用 Application.Run() 来执行调度程序循环,Dispatcher 实际上也不会调度任何东西,包括 Tick 事件。

您需要确保在创建 DispatcherTimer 对象时,创建该对象的代码在调度程序线程中执行,这是您的主 UI 线程。

有几种方法可以做到这一点。恕我直言,最好的方法是让你的 ThreadManager class not a static class 并在你的MainWindow 构造函数。例如:

class TimerManager
{
    DispatcherTimer disTimer;
    Model m = Model.GetInstance();

    public TimerManager()
    {
        disTimer = new DispatcherTimer();
        disTimer.Tick += disTimer_tick;
        disTimer.Interval = new TimeSpan(0, 0, 1);
    }

    public void StartTimer()
    {
        disTimer.Start();
    }

    public void StopTimer()
    {
        disTimer.Stop();
    }

    private void disTimer_tick(object sender, EventArgs e)
    {
        m.Tick++;
    }
}

和:

public partial class MainWindow : Window
{
    TimerManager _timerManager = new TimerManager();

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = Model.GetInstance();
    }

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            _timerManager.StartTimer();
        });
    }

    private void stopButton_Click(object sender, RoutedEventArgs e)
    {
        _timerManager.StopTimer();
    }
}

由于您知道必须在调度程序线程中创建 MainWindow 对象,并且您知道非静态字段初始化发生在调用构造函数的同时,因此在同一个调度程序线程中,以上确保您的 TimerManager 对象是在调度程序线程中创建的。

这使您可以完全控制 TimerManager 对象的生命周期,尤其是在它创建时,当然也包括在它可以被丢弃时。鉴于 DispatcherTimer 对象本身的性质,我认为这比维护静态持有的实例更好。

这种方法还为您提供了为每个调度程序线程拥有一个管理器对象的选项(在极少数情况下,一个程序可能有多个……您应该非常努力地避免陷入这种情况,但它可以是对于至少与这种情况兼容的类型很有用。

就是说,如果您 真的 想要保留 static 实现,您可以通过提供一个可以在您想要初始化时显式调用的方法来实现class,因此您可以确保初始化发生在正确的线程中:

static class TimerManager
{
    static DispatcherTimer disTimer;
    static Model m = Model.GetInstance();

    public static void Initialize()
    {
        disTimer = new DispatcherTimer();
        disTimer.Tick += disTimer_tick;
        disTimer.Interval = new TimeSpan(0, 0, 1);
    }

    public static void StartTimer()
    {
        disTimer.Start();
    }

    public static void StopTimer()
    {
        disTimer.Stop();
    }

    private static void disTimer_tick(object sender, EventArgs e)
    {
        m.Tick++;
    }
}

然后在你的 MainWindow class:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = Model.GetInstance();
        StaticTimerManager.Initialize();
    }

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            StaticTimerManager.StartTimer();
        });
    }

    private void stopButton_Click(object sender, RoutedEventArgs e)
    {
        StaticTimerManager.StopTimer();
    }
}

你在这里需要做的就是确保你从主 UI 线程调用 Initialize() 方法,你实际上有一个 运行 调度程序,之前 您试图调用 class.

中的其他两个 static 方法之一

这种方法也可以用于多个线程(即如果你有多个调度程序线程),但它会更棘手,特别是如果你希望能够调用 StartTimer() 方法来自实际上 拥有 定时器对象的不同线程。如果你真的遇到那种情况,我建议不要使用 static class 方法。