c# - 新的 DispatcherTimer 与旧的一起创建,只有新的应该运行

标签 c# wpf mvvm timer dispatchertimer

前几天我遇到了一个问题。我已经找到了为什么会这样,但是我从来没有遇到过这样的问题,所以我不知道如何解决它。

我有一个应用程序,其中 DashboardView(主视图)中的 DispatcherTimer 在 DashboardViewModel 中启动。当 Timer 计时,我们从数据库中获取数据,这个列表是 View 和 ViewModel 之间的数据绑定(bind)。当有新数据导致数据库发生变化时,会播放声音。

用户可以转到其他 View 。当用户返回 DashboardView 时,会再次创建 DashboardViewModel 和 DispatcherTimer。
现在有 2 个计时器,它们都触发了 Tick 事件,为用户创造了一个令人困惑的场景。

这是我对应用程序现在发生的情况的观察:
我的计时器每分钟都在滴答作响。当我启动应用程序时,DashboardView #1 打开。 DashboardViewModel #1 启动,DispatcherTimer #1 也启动。
我切换到不同的 View ,并更新数据(一封新电子邮件),因此当计时器滴答作响时,仪表板 View 中的列表将更改并播放声音。
当 Timer #1 为 30 秒时,我切换到 DashboardView,它是新创建的,因此创建了 View&ViewModel&Timer #2。
1 分钟后,Timer #1 滴答作响,有新数据,因此它会更新数据库并播放声音,但 View 中的列表没有更新。
我认为这是因为 View #2 显示在#1 之上。我知道,否则我会看到一个叠加层,说它令人耳目一新。
View #2 数据绑定(bind)到 ViewModel #2。 Timer #1 更新了 ViewModel #1,所以更改不会显示,因为我们看不到 View #1,因为它被 View #2 替换/重叠。
1 分 30 秒后,Timer #2 滴答作响,从 DB 获取数据,不播放声音,因为 DB 已由 Timer #1 更新,并以新状态显示数据。
(我希望这是有道理的)

所以,TLDR:有 2 个计时器正在运行,而只有 1 个应该处于事件状态(我认为是最新的)。
我怎样才能做到这一点?


这是我现在拥有的 DashboardViewModel 的(部分):

namespace QRM.ViewModel
{
    class DashboardListViewModel : INotifyPropertyChanged
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        DBServer dbServer = new DBServer();

        #region Constructor
        public DashboardListViewModel()
        {
            log.Info("Dashboard Initializing - Starting...");
            MyObservableCollection<View_server> listDashboard = new MyObservableCollection<View_server>();
            ListDashboard = dbServer.ReadDashboard();
            listBoxCommand = new RelayCommand(() => SelectionHasChanged());

            // Refresh to get all new emails, errors, etc.
            GetListDashboard();

            IsRefreshing = Visibility.Collapsed;

            // Make a timer to renew the data in the Dashboard automatically. 
            DispatcherTimer timer = new DispatcherTimer();
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = Properties.Settings.Default.Timer_interval; // hours, minutes, seconds.
            timer.Start();

            //Receive the Notification sent after DashboardDetailsViewModel has handled the button commands, and call a respond method for the List.
            App.Messenger.Register("RefreshServers", (Action)(() => GetListDashboard()));
            App.Messenger.Register("ClearSelection", (Action)(() => SelectedServer = null));
            App.Messenger.Register("ErrorSolved", (Action)(() => KeepSelection(selectedServer)));
            App.Messenger.Register("WarningSound", (Action)(() => HasNewError = true));
            log.Info("Dashboard Initializing - Done.");
        }
        #endregion

        #region Get list dashboard
        private void GetListDashboard()
        {
            HasNewError = false;
            log.Info("Dashboard - Checking for Email...");

            // The old Outlook class and methods
            //EmailManager checkMail = new EmailManager();
            //checkMail.GetEmail();

            // First, check for mail.
            IMAPManager checkMail = new IMAPManager();
            checkMail.GetEmail();

            log.Info("Dashboard - Checking for linked Errors...");
            // Check if the emails have Errors linked to them. If not, add the Error from the Email to the DB
            ErrorManager checkError = new ErrorManager();
            checkError.GetNewErrors();

            log.Info("Dashboard List - Starting...");
            // Load the dashboard.
            ListDashboard = dbServer.ReadDashboard();
            System.Diagnostics.Debug.WriteLine("REFRESHED THE DASHBOARD");
            log.Info("Dashboard List - Done.");
        }

        private void KeepSelection(View_server keepSelection)
        {
            GetListDashboard();
            SelectedServer = keepSelection;
            SelectionHasChanged();
        }
        #endregion

        #region Timer
        //This method runs every time the timer ticks.
        private async void timer_Tick(object sender, EventArgs e)
        {
            log.Info("Dashboard - Refreshing...");
            System.Diagnostics.Debug.WriteLine(">>Timer tick");
            IsRefreshing = Visibility.Visible;

            // To make sure the overlay is visible to the user, let it be on screen for at least a second (2x half a second)
            await Task.Delay(500);

            if (selectedServer != null)
            {
                KeepSelection(selectedServer);
            }
            else
            {
                GetListDashboard();
            }

            // 2nd half second.
            await Task.Delay(500);
            IsRefreshing = Visibility.Collapsed;

            if (hasNewError == true)
            {
                System.Diagnostics.Debug.WriteLine("List has new error");
                PlayWarningSound();
                HasNewError = false;
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("List has no new error");
                HasNewError = false;
            }
            System.Diagnostics.Debug.WriteLine(">>End timer");

            log.Info("Dashboard - Refreshed.");
        }        
        #endregion
    }
}

最佳答案

这里有几个问题。让我们先从最基本的开始:

清理

DashboardListViewModel被处置或关闭,您需要断开您的 DispatcherTimer.Tick事件处理程序,调用 .Stop()然后调用.Finalize() . MSDN .这将确保您的 System.Windows.Threading.DispatcherTimer已妥善清理。

异步/等待和事件处理程序

此外,DispatcherTimer.Tick事件处理程序定义为 async void .这是 async 的错误用法关键词。而是使用这个:

private void timer_Tick(object sender, EventArgs e)
{
    log.Info("Dashboard - Refreshing...");
    System.Diagnostics.Debug.WriteLine(">>Timer tick");
    IsRefreshing = Visibility.Visible;

    // To make sure the overlay is visible to the user, let it be on screen for at least a second (2x half a second)
    Thread.Sleep(500);

    if (selectedServer != null)
    {
        KeepSelection(selectedServer);
    }
    else
    {
        GetListDashboard();
    }

    // 2nd half second.
    Thread.Sleep(500);
    IsRefreshing = Visibility.Collapsed;

    if (hasNewError == true)
    {
        System.Diagnostics.Debug.WriteLine("List has new error");
        PlayWarningSound();
        HasNewError = false;
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("List has no new error");
        HasNewError = false;
    }
    System.Diagnostics.Debug.WriteLine(">>End timer");

    log.Info("Dashboard - Refreshed.");
}

我通常从不建议使用 Thread.Sleep但是由于您已经在线程计时器的上下文中,这是有道理的。

最后一个问题

你确定App.Messenger.Register可以多次调用,因为每次实例化 View 模型时都会发生这种情况?我会想象这将是你只想做一次的事情,在 static语境。

关于c# - 新的 DispatcherTimer 与旧的一起创建,只有新的应该运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36864779/

相关文章:

c# - 为什么 Int32.MinValue - 1 返回 Int32.MaxValue?

c# - 如何将 UI RawImage 转换为字节数组

.net - 对齐 ListBoxItem 内容

wpf - 依赖注入(inject) : Assigning User Controls to Specific Grid Cells

c# - Asp.net Web API 规范化参数

c# - 如何将字符串数组拆分为新的字符串数组并删除重复项

WPF:当菜单项被禁用时,图标不会变灰是标准的吗?

wpf - 在 View 中使用 DataTemplate 会在 View 和 ViewModel 之间创建耦合吗?

c# - 在wpf C#中禁用自定义父级时启用子控件

wpf - 如何在 Prism 框架中加载实际 shell 之前显示登录屏幕