Thread: How to get my program to gracefully wait for some calculations to be done?

  1. #1
    Registered User
    Join Date
    Oct 2009
    Posts
    5

    How to get my program to gracefully wait for some calculations to be done?

    I'm working on my first non-trivial home project in C# (have a C++ background). Part of this invloves hitting my "Calculate" button at which point the program requires about 30 seconds to do the required calculations.

    I managed to cobble together a progress bar which gives the user some indication of how things are progressing, and I (think I) have the work happening in its own thread.

    However, currently my "Processing Complete" message box appears immediately, rather than waiting for work to complete, and also the entire form is still accessible to the user during this processing stage (while most interaction with this form would be harmless, hitting "Calculate" again causes all kinds of bizzare things to happen).

    Ideally I want my "processing complete" message to appear at the right time, and for the form to be non-interactive during processing. I have tried several different methods, but most seem to hang the program at progress = 0.

    Here is some of my code as it currently stands:

    The click event for starting the calculations:

    Code:
     private void button1_Click(object sender, EventArgs e)
            {
                ui_CalculateButton.Visible = false;
                progressBar1.Visible = true;
                this.progressBar1.Minimum = 0;
                this.progressBar1.Maximum =(int)resolution.Value;
                this.progressBar1.Step = 1;
                this.progressBar1.Value = 0;
                this.progressBar1.Visible = true;           
    
                calculationThread = new Thread(new ThreadStart(this.calculationTask));
                calculationThread.IsBackground = true;
                calculationThread.Start();           
    
                // tried several things, e.g. Join() here but nothing seems to work
           
                MessageBox.Show("Calculation complete", "Finished processing", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                progressBar1.Visible = false;
                ui_CalculateButton.Visible = true;
            }
    The working thread:

    Code:
            private void calculationTask()
            {
    
                while (buffer.Calculation()) // returns true until finished
                {
                    this.increment();     
                    Thread.Sleep(0);
                };
    
                buffer.save();
                this.increment();
                this.calculationThread.Abort();  // think this is part of attempts
                // to get it working as I want
            }
    Then a few functins to do with getting the progress bar working which may or may not be relevant. Certainly, I have only the most vague ideas of what's going on in these two at my current level of proficiency

    Code:
            private void incrementProgressBar()
            {
                this.progressBar1.Increment(1);
            }
    
    
            private void increment()
            {
                // InvokeRequired required compares the thread ID of the
                // calling thread to the thread ID of the creating thread.
                // If these threads are different, it returns true.
                if (this.progressBar1.InvokeRequired)
                {
                    setIncrementCallback d = new setIncrementCallback(incrementProgressBar);
                    this.Invoke(d, new object[] {});
                }
                else
                {
                    this.progressBar1.Increment(1);
                }
            }

    I'd really appreciate any help you can offer.

  2. #2
    Registered User
    Join Date
    Mar 2009
    Location
    england
    Posts
    209
    You're kinda almost there, but set the visibility properties from within the worker thread rather than from the main thread.

    In other words...

    Code:
    MessageBox.Show("Calculation complete", "Finished processing", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
    progressBar1.Visible = false;
    ui_CalculateButton.Visible = true;
    ...should be at the end of the calculationTask function.

    Note: You'll probably have to invoke the progressbar and the button before being able to change their visibility properties otherwise you might run into cross threading exceptions.

  3. #3
    (?<!re)tired Mario F.'s Avatar
    Join Date
    May 2006
    Location
    Ireland
    Posts
    8,446
    I'm unsure as to the objective behind creating a new thread. But if this is because you wish for the UI to remain responsive and allow the user to go about doing other things while thje calculation takes place, use a BackgroundWorker instead.

    1. Add a BackroundWorker instance

    Code:
    using System.ComponentModel;
        
    public partial class MainForm : Form
    {
        /*...*/
        
        private BackgroundWorker bgWorker = new BackgroundWorker();
        
        /*...*/
    }
    2. Subscribe to key BackgroundWorker events

    Code:
    bgWorker.WorkerReportsProgress = true;
    bgWorker.WorkerSupportsCancellation = false;
    
    bgWorker.DoWork  +=  bgWorker_DoWork;
    bgWorker.RunWorkerCompleted  +=  bgWorker_RunWorkerCompleted;
    bgWorker.ProgressChanged += bgWorker_ProgressChanged;
    3. Create the events

    Code:
    private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        // This is where you code the actual task.
        // I prefer to have it on its separate method and call it from here.
        // In this example I'm also passing the argument we are getting from point 4. below
       // The return statement is essential for proper handling of RunWorkerCompleted or if
       // you wish to implement a background task cancellation feature.
    
        e.Result = DoSomething((string)e.Argument, sender);
    
        //note: in here you are already on a separate thread. Do not access your objects from
        //here. Only from the two events below.
    }
        
    private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // This is where you put what to do when the Background Worker is done.
        // Here we are back to the main thread. So you are free to access your UI objects.
        if (e.Error != null)
            // always do this check. Exceptions that may happen inside DoWork are finally
            // processed here. Check e.Error for the actual exception, if any.
        else
        {
            // all is well. The thread is finished and we can add any final code in here
        }
    }
    
    private void bgWorker_ProgressChanged(object sender,
                ProgressChangedEventArgs e)
    {
        //the progressChanged event is called at your discretion from within your DoWork code.
        //Or, on this example case from within DoSomething as you willl see below.
        //While within this event you are back at the main thread, so you can update controls,
        // read controls and such.
        this.progBar.Value = e.ProgressPercentage;
    }
    4. Create the method that will do the actual work as normal

    Code:
    private DoSomething(string str, object sender)
    {
        // Your actual work. I prefer to not populate DoWork and instead create a separate
        // method and call it from DoWork.
        
        Thread.Sleep(2000);
    
        //If you look back at DoWork, I'm also passing here the worker object.
        //time to use it now to report the progress.
        //If you don't need to report progress, you don't need to pass it
        BackgroundWorker worker = sender as BackgroundWorker;
        
        //here we fire up the ProgressChanged Event above
        worker.ReportProgress(50);
    
        Thread.Sleep(2000);
    }
    5. All Done. How to Implement?

    Code:
    // Simply call RunWorkerAsync() from anywhere in your code where you need this work to
    start.
    // On this example I decided to also pass a string argument. This string is sent to DoWork
    // and readable from within through e.Argument as you can see above. And then passed
    // onto DoSomething.
    // Because DoWorkEventArgs.Argument is actually of the object type, you can pass
    // anything. Including your own objects.
    
    bgWorker.RunWorkerAsync("Hello World!");
    This should get you started.
    Check your documentation on the BackgroundWorker for more information.
    Last edited by Mario F.; 12-21-2009 at 08:36 PM.
    Originally Posted by brewbuck:
    Reimplementing a large system in another language to get a 25% performance boost is nonsense. It would be cheaper to just get a computer which is 25% faster.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Date program starts DOS's date
    By jrahhali in forum C++ Programming
    Replies: 1
    Last Post: 11-24-2003, 05:23 PM
  2. Replies: 2
    Last Post: 07-25-2003, 03:01 AM
  3. Replies: 2
    Last Post: 05-10-2002, 04:16 PM
  4. How to make a program wait for an event?
    By DRoss in forum C++ Programming
    Replies: 0
    Last Post: 09-10-2001, 01:13 PM
  5. How can I make a program wait for an event?
    By DRoss in forum C++ Programming
    Replies: 0
    Last Post: 09-06-2001, 09:28 AM