Новый DispatcherTimer создается вместе со старым, должен запускаться только новый

Я столкнулся с проблемой на днях. Я узнал, почему это происходит, но я никогда не сталкивался с такой проблемой, поэтому я не знаю, как ее решить.

У меня есть приложение, в котором в DashboardView (основном представлении) DispatcherTimer запускается в DashboardViewModel. Когда таймер тикает, мы получаем данные из базы данных, этот список привязан к данным между View и ViewModel. При появлении новых данных, вызвавших изменение базы данных, будет воспроизводиться звук.

Пользователь может перейти к другим представлениям. Когда пользователь возвращается к DashboardView, снова создается DashboardViewModel и DispatcherTimer.
Теперь есть 2 таймера, и они оба запускают событие Tick, создавая запутанный сценарий для пользователя.

Мое наблюдение за тем, что происходит в приложении прямо сейчас:
Мой таймер тикает каждую минуту. Когда я запускаю приложение, открывается DashboardView #1. Запускается DashboardViewModel #1, а также DispatcherTimer #1.
Я переключаюсь на другое представление и обновляю данные (новый адрес электронной почты), поэтому, когда таймер тикает, список в DashboardView изменится, и появится звук. воспроизводится.
Когда таймер №1 достигает 30 секунд, я переключаюсь на вновь созданный DashboardView, таким образом создавая View&ViewModel&Timer №2. Через 1 минуту срабатывает Таймер №1, появляются новые данные, поэтому он обновляет БД и воспроизводит звук, но список в представлении не обновляется. Я думаю, это потому, что вид № 2 отображается поверх № 1. Я знаю, потому что в противном случае я бы увидел наложение, говорящее об обновлении.
Представление № 2 привязано к данным ViewModel № 2. Таймер № 1 обновил ViewModel № 1, поэтому изменения не будут отображаться, поскольку мы не можем видеть представление № 1, поскольку оно заменено/перекрывается представлением № 2. Через 1 мин 30 секунд Таймер №2 тикает, получает данные из БД, не воспроизводит звук, так как БД уже была обновлена ​​Таймером №1, и показывает данные в новом состоянии.< br> (надеюсь, это имело смысл)

Итак, 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
    }
}

person Kailayla    schedule 26.04.2016    source источник
comment
Когда пользователь возвращается к DashboardView, снова создается DashboardViewModel. Не делай этого. Вместо этого повторно используйте существующий экземпляр модели представления. В качестве альтернативы, если вы не можете обойти создание нового экземпляра модели представления, обязательно остановите таймер в предыдущем экземпляре, например. реализуя IDisposable и, конечно же, вызывая его метод Dispose(), где вы останавливаете таймер.   -  person Clemens    schedule 26.04.2016
comment
Ах. Да, переключение представления - это то, что я получил из другого источника, поэтому мне придется изучить этот код, чтобы увидеть, где создается представление (модель) и/или переключается и как. Но я думаю, что смогу это выяснить.   -  person Kailayla    schedule 26.04.2016


Ответы (1)


Здесь есть несколько проблем. Начнем сначала с самого основного:

Очистка

Когда 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 можно вызывать несколько раз, поскольку это происходит каждый раз, когда создается экземпляр вашей модели представления? Я бы предположил, что это будет то, что вы захотите сделать только один раз, в контексте static.

person David Pine    schedule 26.04.2016