Search code examples
c#wpfmultithreadingtimerbackgroundworker

Timer Elapsed Event does not perform all actions


I have a Backgroundworker which collects a large amount of data from a database. I want to set a timeout limit on the thread so that if it does not return the results after a set amount of time the process is cancelled.

I start a timer on the main thread at the same time as I start the BackgroundWorker.

If I purposely make the BGW sleep, the timer elapses and calls the .Elapsed event. It then cancels the Background worker, however it does not successfully complete the other actions I want it to complete, which are update the status bar on the GUI and throw up MessageBox. I don't understand why not, can anyone help?

Time out and sleep are purposely set for testing.

        /// <summary>
        /// loads Assets by group ID
        /// Populates Grid on asset listing page
        /// </summary>
        /// <param name="groupId"></param>
        /// <param name="user"></param>
        internal void PopulateGridAssetsByGroup(int groupId, User user)
        {
            //update statusbar
            AfMainWindow.MainWindow.UpdateStatusBar("Loading Assets...");

            //setup BG worker
            populateGridAssetsWorker = new BackgroundWorker {WorkerSupportsCancellation = true, WorkerReportsProgress = false};
            populateGridAssetsWorker.DoWork += populateGridAssetsWorker_DoWork;
            populateGridAssetsWorker.RunWorkerCompleted += populateGridAssetsWorker_RunWorkerCompleted;

            //setup timer which will cancel the background worker if it runs too long
            cancelTimer = new Timer {Interval = 2000, Enabled = true, AutoReset = false};
            cancelTimer.Elapsed += cancelTimer_Elapsed;
            cancelTimer.Start();

            populateGridAssetsWorker.RunWorkerAsync(groupId); //start bg worker
        }


        void cancelTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            populateGridAssetsWorker.CancelAsync();
            cancelTimer.Stop();

            AfMainWindow.MainWindow.UpdateStatusBar("Could not load assets, timeout error");

            MessageBox.Show("Operation Timed Out\n\nThe Server did not respond quick enough, please try again",
                            "Timeout", MessageBoxButton.OK, MessageBoxImage.Exclamation, MessageBoxResult.OK);

            AfMainWindow.MainWindow.Busy.IsBusy = false;

        }

        /// <summary>
        /// when bg worker complete, update the AssetGrid on Asset Listing Page with results
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void populateGridAssetsWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

            cancelTimer.Stop();

            //my thread complete processing

        }        
        /// <summary>
        /// Perform the DB query and collect results for asset listing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void populateGridAssetsWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            var assetListByGroup = new List<AssetLinked>();

            Thread.Sleep(5000);

            if (populateGridAssetsWorker.CancellationPending)
            {
                return;
            }

            try
            {

              //My SQL actions 

            if (populateGridAssetsWorker.CancellationPending)
            {
                return;
            }
            }
            catch (Exception ex)
            {
                Globals.AFWideSettings.GeneralErrorMessage(ex.Message);
            }
        }

Solution

  • I suppose your cancelTimer_Elapsed method silently crashes when you try to update the GUI from another thread. Exceptions thrown in background threads are sometimes hard to find without debugging with Visual Studio set to break on all exceptions - you can set that in the menu Debug > Exceptions and checking all CLR exceptions. Alternatively you can wrap the method body in a try block and put breakpoint in the catch block.

    void cancelTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
       try
       {
            populateGridAssetsWorker.CancelAsync();
            cancelTimer.Stop();
    
            AfMainWindow.MainWindow.UpdateStatusBar("Could not load assets, timeout error");
    
            MessageBox.Show("Operation Timed Out\n\nThe Server did not respond quick enough, please try again",
                            "Timeout", MessageBoxButton.OK, MessageBoxImage.Exclamation, MessageBoxResult.OK);
    
            AfMainWindow.MainWindow.Busy.IsBusy = false;
        }
        catch(Exception ex)
        {
            int breakpoint = 42;
        }
    }
    

    I'm by no means sure this is the case, but it's worth a try in my opinion.