簡體   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