3

**Ultimately I am going to have four tasks running concurrently and have another form that contains four progress bars. I would like for each progress bar to update as it's work task is completing.

Here's what I'm trying to do for starters.

I have a form that has some buttons on it. When I click one I'm creating a new task to do some work.

public partial class MyMainForm : Form
{

    private void btn_doWork_Click(object sender, EventArgs e)
    {
        Task task = new Task(RunComparisons);
        task.Start();
    }

    private void RunComparisons()
    {
        int progressBarValue = 0;
        MyProgressBarForm pBar = new MyProgressBarForm(maxValue, "some text");
        pBar.ShowDialog();
        foreach(string s in nodeCollection)
        {
            //do some work here
            progressBarValue++;
            pBar.updateProgressBar(progressBarValue, "some new text");
        }
        pBar.BeginInvoke(new Action(() => pBar.Close()));
    }
}

In another class that contains a form with a progress bar:

public partial class MyProgressBarForm : Form
{
    public MyProgressBarForm(int maxValue, string textToDisplay)
    {
        InitializeComponent();
        MyProgressBarControl.Maximum = maxValue;
        myLabel.Text = textToDisplay;
    }

    public void updateProgressBar(int progress, string updatedTextToDisplay)
    {
        MyProgressBarForm.BeginInvoke(
            new Action(() =>
            {
                MyProgressBarControl.Value = progress;
                myLabel.Text = updatedTextToDisplay;
            }));
    }

When I click the doWork button the progress bar form displays but doesn't update. It just sits there and hangs. If I comment out the pBar.ShowDialog(); then the progress bar form doesn't display but the work to be done is run to completion perfectly.

I had this working perfectly when I was creating my own threads but I read about Tasks and now I'm trying to get this to run that way. Where did I go wrong?

3 Answers3

18

The TPL adds the IProgress interface for updating the UI with the progress of a long running non-UI operation.

All you need to do is create a Progress instance in your UI with instructions on how to update it with progress, and then pass it to your worker which can report progress through it.

public partial class MyMainForm : System.Windows.Forms.Form
{
    private async void btn_doWork_Click(object sender, EventArgs e)
    {
        MyProgressBarForm progressForm = new MyProgressBarForm();
        progressForm.Show();
        Progress<string> progress = new Progress<string>();
        progress.ProgressChanged += (_, text) =>
            progressForm.updateProgressBar(text);
        await Task.Run(() => RunComparisons(progress));
        progressForm.Close();
    }
    private void RunComparisons(IProgress<string> progress)
    {
        foreach (var s in nodeCollection)
        {
            Process(s);
            progress.Report("hello world");
        }
    }
}
public partial class MyProgressBarForm : System.Windows.Forms.Form
{
    public void updateProgressBar(string updatedTextToDisplay)
    {
        MyProgressBarControl.Value++;
        myLabel.Text = updatedTextToDisplay;
    }
}

This lets the Progress Form handle displaying progress to the UI, the working code to only handle doing the work, the main form to simply create the progress form, start the work, and close the form when done, and it leaves all of the work of keeping track of progress and marhsaling through the UI thread to Progress. It also avoids having multiple UI thread; your current approach of creating and manipulating UI components from non-UI threads creates a number of problems that complicates the code and makes it harder to maintain.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Didn't know that `TPL` has option of `Progress`. This should be accepted answer. – Faizan Mubasher Jan 10 '18 at 10:03
  • 1
    How would I dynamically pass the progressBar maximum to the MyProgressBarForm? I've tried creating a method in MyProgressBarForm in which gets called from Form1 to set the progressBar maximum. When I do it, I get an error about another thread accessing the form error. I know what is causing it it but I dont know a work around. Any thoughts would be greatly appreciated it. – NuWin Aug 22 '18 at 04:06
2

Create your progress bar form on the main UI thread of the parent form, then call the Show() method on the object in your button click event.

Here's an example with 2 bars:

//In parent form ...
private MyProgressBarForm progressBarForm = new MyProgressBarForm();

private void button1_Click(object sender, EventArgs e)
{
    progressBarForm.Show();
    Task task = new Task(RunComparisons);
    task.Start();
}

private void RunComparisons()
{
    for (int i = 1; i < 100; i++)
    {
        System.Threading.Thread.Sleep(50);
        progressBarForm.UpdateProgressBar(1, i);
    }
}

//In MyProgressBarForm ...
public void UpdateProgressBar(int index, int value)
{
    this.Invoke((MethodInvoker) delegate{
        if (index == 1)
        {
            progressBar1.Value = value;
        }
        else
        {
            progressBar2.Value = value;
        }
    });
}
Setsu
  • 1,188
  • 13
  • 26
  • Just one question - the maxValue of the progress bar will be variable. It could be 5 or 200. If I declare the progressbarForm without passing the max value to the constructor how do I set the max? – UmarSlobberknocker Feb 13 '15 at 21:34
  • @UmarSlobberknocker There are a number of ways to accomplish that. One way is to make a new method in MyProgressBarForm that sets a new max for a given progress bar, and then you call it before you start updating (If you're not doing this on the UI thread, you'd need to invoke again). – Setsu Feb 13 '15 at 21:38
  • 1
    question. When I was running my compare functionality from a thread that I created my program ran ridiculously fast, but now that I have it running as a task it takes forever to complete. Is a task that much slower than a tread? – UmarSlobberknocker Feb 16 '15 at 15:08
  • @UmarSlobberknocker See [this question](http://stackoverflow.com/questions/13429129/task-vs-thread-differences). Also, you should really start a new question for this. – Setsu Feb 17 '15 at 19:02
-1

.ShowDialog is a blocking call; execution won't continue until the dialog returns a result. You should probably look in to a BackgroundWorker to process the work on another thread and update the dialog.

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
  • Will BackGroundWorker work while the work is being done in a task? – UmarSlobberknocker Feb 13 '15 at 21:00
  • @UmarSlobberknocker I think he means to just abandon the task approach and do your work on the BackgroundWorker's DoWork method – Setsu Feb 13 '15 at 21:00
  • I would do the work inside the background worker instead of a Task. Using Task doesn't automatically make your code asynchronous. – Bradley Uffner Feb 13 '15 at 21:00
  • Well, ultimately I'm going to have upwards of 4 tasks that I would like to all run concurrently. That's why I'm asking. I'll have a form with four progress bars and would like to get each progress bar to update as each work task is completing. – UmarSlobberknocker Feb 13 '15 at 21:03
  • 2
    Using a BGW would be taking a big step back, it would make the code quite a bit more bloated than what it actually needs to be. The TPL is a noticeable improvement. – Servy Feb 13 '15 at 21:54