-1

Good morning, I'm trying to write an application that use in his interface a progressbar (in C#, WPF). I have read about the need of perform the UI task in a different thread, using Backgroundworker. I trying to make it work using a lot of information, but nothing happens (the program work fine, but the progressbar only shown at the end of the "hard-work tasks").

I'm civil engineer (not a software one), so I ask if anyone can help me with that.

namespace SAP2000___Quake_Definitions
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private readonly BackgroundWorker bgWoker = new BackgroundWorker();


    public MainWindow()
    {
        InitializeComponent();

        this.bgWoker.WorkerReportsProgress = true;
        this.bgWoker.WorkerSupportsCancellation = true;

        this.bgWoker.DoWork += bgWorker_DoWork;
        this.bgWoker.ProgressChanged += bgWorker_ProgressChanged;
        this.bgWoker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
    }

    private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.progBar.Value = e.ProgressPercentage;
    }

    private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bgWorker = (BackgroundWorker)sender;            
        Dispatcher.Invoke(new Action(() => DoTheHardWork()));
    }

    private void processButton_Click(object sender, RoutedEventArgs e)
    {
       this.bgWoker.RunWorkerAsync();
    }

    private void DoTheHardWork()
    {
    switch (this.chckBox2.IsChecked.GetValueOrDefault())
        {
            case true:
            this.bgWoker.ReportProgress(0);
            //more hardwork with inputs from WPF

            case false:
            this.bgWoker.ReportProgress(0);
            //more hardwork with inputs from WPF
        }
    }
}
}
Asrhael
  • 149
  • 1
  • 1
  • 7
  • I'm not an WPF guy, but I don't think you're supposed to invoke in the DoWork. That will push the work to the UI thread, so you lose the benefit of the background thread. – LarsTech Mar 11 '18 at 22:34
  • 1
    Read the docs how to use the `BackgroundWorker`. This is also an ' old way' how to do work on a background thread. Look into `Task.Run`. It's much cleaner and easier to do this kind of thing. – Sievajet Mar 11 '18 at 22:36
  • @Sievajet: While it is about as dated as Windows Forms, it is still a good beginners tool. This looks like beginner mistakes, so better to keep it simple for now. But overall I agree: The other approaches are better overall/longterm. – Christopher Mar 11 '18 at 22:41

2 Answers2

2

That is not how you should be using a BackgroundWorker. I wrote some example code a few years back. It should get you on the right track:

#region Primenumbers
private void btnPrimStart_Click(object sender, EventArgs e)
{
    if (!bgwPrim.IsBusy)
    {
        //Prepare ProgressBar and Textbox
        int temp = (int)nudPrim.Value;
        pgbPrim.Maximum = temp;
        tbPrim.Text = "";

        //Start processing
        bgwPrim.RunWorkerAsync(temp);
    }
}

private void btnPrimCancel_Click(object sender, EventArgs e)
{
    if (bgwPrim.IsBusy)
    {
        bgwPrim.CancelAsync();
    }
}

private void bgwPrim_DoWork(object sender, DoWorkEventArgs e)
{
    int highestToCheck = (int)e.Argument;
    //Get a reference to the BackgroundWorker running this code
    //for Progress Updates and Cancelation checking
    BackgroundWorker thisWorker = (BackgroundWorker)sender;

    //Create the list that stores the results and is returned by DoWork
    List<int> Primes = new List<int>();


    //Check all uneven numbers between 1 and whatever the user choose as upper limit
    for(int PrimeCandidate=1; PrimeCandidate < highestToCheck; PrimeCandidate+=2)
    {
        //Report progress
        thisWorker.ReportProgress(PrimeCandidate);
        bool isNoPrime = false;

        //Check if the Cancelation was requested during the last loop
        if (thisWorker.CancellationPending)
        {
            //Tell the Backgroundworker you are canceling and exit the for-loop
            e.Cancel = true;
            break;
        }

        //Determin if this is a Prime Number
        for (int j = 3; j < PrimeCandidate && !isNoPrime; j += 2)
        {
            if (PrimeCandidate % j == 0)
                isNoPrime = true;
        }

        if (!isNoPrime)
            Primes.Add(PrimeCandidate);
    }

    //Tell the progress bar you are finished
    thisWorker.ReportProgress(highestToCheck);

    //Save Return Value
    e.Result = Primes.ToArray();
}

private void bgwPrim_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    pgbPrim.Value = e.ProgressPercentage;
}

private void bgwPrim_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    pgbPrim.Value = pgbPrim.Maximum;
    this.Refresh();

    if (!e.Cancelled && e.Error == null)
    {
        //Show the Result
        int[] Primes = (int[])e.Result;

        StringBuilder sbOutput = new StringBuilder();

        foreach (int Prim in Primes)
        {
            sbOutput.Append(Prim.ToString() + Environment.NewLine);
        }

        tbPrim.Text = sbOutput.ToString();
    }
    else 
    {
        tbPrim.Text = "Operation canceled by user or Exception";
    }
}
#endregion

You have to limit all UI writing work to the Progress Report and Run wokrer compelte Events. Those will be raised in the thread that created the BGW (wich should be the UI thread) automagically.

Note that you can only report progress between distinct steps. I had the advantage that I had to write the loop anyway. But if you have existing code (like most download or disk code), you can usually only report between files.

Christopher
  • 8,956
  • 2
  • 14
  • 31
  • Thank you Christopher. I made this changes to my code in the DoWork event handler adding BackgroundWorker bgWorker = (BackgroundWorker)sender and the UI work fine for a simple loop, but with my whole code shown the exception: 'The calling thread cannot access this object because a different thread owns it.' – Asrhael Mar 12 '18 at 00:11
  • @Asrhael: While most classes do not care from wich thread they are written, GUI elements do. We added that intentionally to force people to invoke. Because the alternative was worse (http://stackoverflow.com/a/14703806). BackgroundWorker already invokes the RunWorkerCompleted and Progress Report Events for you. Confine all UI writing to those two events. If you still get an exception, something is horribly messed up in your whole design. Start from scratch and keep it simple rather then try to untangle such a mess. – Christopher Mar 12 '18 at 01:23
  • Thank you @Christopher. My mistake was to use a direct value from a component of the form directly inside of the DoWork handle (this.chckBox2.IsChecked.GetValueOrDefault()). If I save that bool value in a variable, instead using directly, the code work fine! and the UI is totally responsive now. Now I know that is not fine to manipulate directly a control inside a backgroundworker enviroment. – Asrhael Mar 12 '18 at 03:02
0

my mistakes were three:

  1. Trying to use "Dispatcher.Invoke(new Action(() => DoTheHardWork()));" to solve an exception related to my thread (exception caused by point #3).
  2. Avoiding the instantiation: BackgroundWorker bgWorker = (BackgroundWorker)sender (thank you @Christopher).
  3. Writing a code that manipulate a UI-Component inside the DoWork event handle of my Backgroundworker. MSDN says: You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events. Trying this, the exception occur.

Solving the point #2 and #3, the UI is perfectly responsive respect to the "hardwork" function (runned in background).

Asrhael
  • 149
  • 1
  • 1
  • 7