繁体   English   中英

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

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

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

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类来表示 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类:

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

除了,线程池线程没有Dispatcher s。 因此,那里没有Dispatcher来引发DispatcherTimer对象的Tick事件。 即使有,如果不调用Application.Run()来执行调度程序循环, Dispatcher实际上也不会调度任何内容,包括Tick事件。

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

有几种方法可以做到这一点。 恕我直言,最好的方法是让您的ThreadManager不是static类,并在您的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实现,你可以通过提供一个可以在你想要初始化类时显式调用的方法来实现,这样你就可以确保初始化发生在正确的线程中:

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类中:

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

您在这里需要做的就是确保在您尝试调用类中的其他两个static方法中的任何一个之前,从实际有一个正在运行的调度程序的主 UI 线程调用Initialize()方法。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM