2

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);
            }
        }
Damo
  • 1,898
  • 7
  • 38
  • 58
  • and the down vote was for what exactly? – Damo Jan 24 '13 at 22:27
  • 1
    +1 to nullify the downvote. Seems like a reasonable question to me. – PeteH Jan 24 '13 at 22:31
  • This seems like a prime candidate for a breakpoint, presumably you did this already? So are you saying that you hit MessageBox.Show....but don't get a message box? – PeteH Jan 24 '13 at 22:35
  • If I set a breakpoint and step through the code, the step though stops and returns back to the application GUI after a matter of seconds. It doesn't matter what order I put these actions in, the The message box and status bar update do not occur. – Damo Jan 24 '13 at 22:38
  • and your breakpoint is at the top of _Elapsed, right? You could try temporarily commenting out RunWorkerAsync and CancelAsync, see if everything happens as expected then. But nothing jumping out at me – PeteH Jan 24 '13 at 22:47
  • 1
    Have you tried setting 'break on exception' and running this? Exceptions in non-main thread might be getting swallowed and preventing the cancel_Timer method running to completion. Perhaps the duplicate calls to cancelTimer.Stop() ? – RJ Lohan Jan 24 '13 at 22:47

1 Answers1

2

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.

Honza Brestan
  • 10,637
  • 2
  • 32
  • 43
  • Ah. did this and added a Debug output to the catch. We have a "The calling thread cannot access this object because a different thread owns it." But the event is on the main thread surely? – Damo Jan 24 '13 at 22:55
  • 1
    Oh... http://stackoverflow.com/questions/1435876/do-c-sharp-timers-elapse-on-a-separate-thread – Damo Jan 24 '13 at 22:58
  • Great, so you've found the issue. The `Timer` actually uses more (ThreadPool) threads - one for the timer itself and at least one for the `Elapsed` handler. So the event is not fired on the main thread, you will have to come up with a mechanism to move the GUI-related code back to the main thread. – Honza Brestan Jan 24 '13 at 22:59
  • Thanks, wasn't expecting that at all – Damo Jan 24 '13 at 23:01
  • try using System.Windows.Threading.DispatcherTimer instead, I am pretty sure this will let you update the GUI. – Rhexis Jan 25 '13 at 01:54