在 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 方法。
我已经为我的 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 方法。