15

I'm stuck trying to update a progressbar from other threads ran in a different class. To explain what I do I think a picture will be better. I want to update the progressbar in the //HERE point :enter image description here

I've tried using a delegate, tried with ReportProgress and I think i've basically tried to use everything google reported in the first 100 results, without success. I'm still learning WPF and this might be silly way to proceed, i'm looking for a quick and dirty way to get the work done but feel free to tell me what I should redesign for a cleaner application.

EDIT : More code.

In ExecutorWindow.xaml.cs :

public void RunExecutor()
{
    // CREATE BACKGROUNDWORKER FOR EXECUTOR
    execBackground.DoWork += new DoWorkEventHandler(execBackground_DoWork);
    execBackground.RunWorkerCompleted += new RunWorkerCompletedEventHandler(execBackground_RunWorkerCompleted);
    execBackground.ProgressChanged += new ProgressChangedEventHandler(execBackground_ProgressChanged);
    execBackground.WorkerReportsProgress = true;
    execBackground.WorkerSupportsCancellation = true;
    // RUN BACKGROUNDWORKER
    execBackground.RunWorkerAsync();
}
private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
    myExecutor = new Executor(arg1, arg2);
    myExecutor.Run();            
}

private void execBackground_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    MessageBox.Show("RunWorkerCompleted execBackground");
}

private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ExecutorProgressBar.Value += 1;
}

// TESTING 
private void updateProgressBar(int i)
{
    ExecutorProgressBar.Value += i;
}

public delegate void callback_updateProgressBar(int i);

In Executor.cs :

public void Run()
{
    string[] options = new string[2];
    int i = 0;

    while (LeftToRun > 0)
    {
        if (CurrentRunningThreads < MaxThreadsRunning)
        {
            BackgroundWorker myThread = new BackgroundWorker();
            myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
            myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
            myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
            myThread.WorkerReportsProgress = true;
            myThread.WorkerSupportsCancellation = true;

            myThread.RunWorkerAsync(new string[2] {opt1, opt2});

            // HERE ?
            CurrentRunningThreads++;
            i++;
            LeftToRun--;

        }
    }

    while (CurrentRunningThreads > 0) { }
    logfile.Close();
    MessageBox.Show("All Tasks finished");
}

private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
    string[] options = (string[])e.Argument;
    string machine = options[0];
    string script = options[1];
    // UPDATE HERE PROGRESSBAR ?
    RemoteProcess myRemoteProcess = new RemoteProcess(machine, script);
    string output = myRemoteProcess.TrueExec();
    // UPDATE HERE PROGRESSBAR ?
    this.logfile.WriteLine(output);
}

private void backgroundWorkerRemoteProcess_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    CurrentRunningThreads--;
}

private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    //myExecWindow.ExecutorProgressBar.Value = e.ProgressPercentage; // TESTING
    //ExecutorWindow.callback_updateProgressBar(1); // TESTING 
}

EDIT 2 : I got it! Simple in fact, but i guess I've been looking too close to find out.

In my ExecutorWindow class :

private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
    myExecutor = new Executor(arg1, arg2);
    myExecutor.Run(sender);
}

private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ExecutorProgressBar.Value += 1;
}

And in my Executor class :

private BackgroundWorker myExecutorWindow;

[...]

public void Run(object sender)
{
            myExecutorWindow = sender as BackgroundWorker;
            string[] options = new string[2];
            int i = 0;

            while (LeftToRun > 0)
            {
                if (CurrentRunningThreads < MaxThreadsRunning)
                {
                    BackgroundWorker myThread = new BackgroundWorker();
                    myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
                    myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
                    myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
                    myThread.WorkerReportsProgress = true;
                    myThread.WorkerSupportsCancellation = true;

                    myThread.RunWorkerAsync(new string[2] {opt1, opt2});

                    CurrentRunningThreads++;
                    i++;
                    LeftToRun--;      
                }
            }

[...]

private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
            myBackgroundWorker.ReportProgress(1);
            // PROCESSING MY STUFF HERE
            myBackgroundWorker.ReportProgress(1);
        }

        private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            myExecutorWindow.ReportProgress(1);
        }

Thank you !

ack__
  • 913
  • 1
  • 14
  • 39
  • Excellent description, +1. Did you draw that manually or with the help of some code-to-diagram tool? – Teoman Soygul Apr 26 '11 at 12:02
  • Bad Description. The code-in-picture can't be scaled or Copy/Pasted. The code also looks incomplete (at crucial points). Where is DoWork? – H H Apr 26 '11 at 12:05
  • @Teoman Soygul : I did it using Visio. @Henk Holterman : True, I can edit with the DoWork code and other function, I didn't think it was relevant to the topic though, that's why I didnt put them. – ack__ Apr 26 '11 at 12:27
  • @Heandel : Yes I've tried it following this tutorial : http://blogs.msdn.com/b/csharpfaq/archive/2004/03/17/91685.aspx . But I didn't understand how to correctly apply this to my code. In the example, he's calling "m_TextBox.Invoke(....)" but I cannot call the progress bar .Invoke from my Executor class. – ack__ Apr 26 '11 at 12:40

4 Answers4

13

You can run any method on the UI thread with this very basic sample

this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate() 
{       
   this.progressBar.Value= 20; // Do all the ui thread updates here
}));

Running commands inside the Dispatcher.Invoke(...), you can actually interact with the UI from any worker thread, where otherwise you would get an exception.

If you really need to have the ultimate control on the background threads & main (UI) thread updates, here is a fantastic tutorial on that: http://blog.decarufel.net/2009/03/good-practice-to-use-dispatcher-in-wpf.html

Teoman Soygul
  • 25,584
  • 6
  • 69
  • 80
  • This would be the solution for a normal (Pool) thread. The Bgw has special support for progress (to avoid this Invoke pattern). – H H Apr 26 '11 at 12:07
1

You should be able to use the Dispatcher.Invoke method

e.g.

    Dispatcher.Invoke(
        new System.Action(() => myProgressBar.Value = newValue)
        );
Mike
  • 661
  • 5
  • 10
1

I got it! Simple in fact, but i guess I've been looking too close to find out.

In my ExecutorWindow class :

private void execBackground_DoWork(object sender, DoWorkEventArgs e)
{
    myExecutor = new Executor(arg1, arg2);
    myExecutor.Run(sender);
}

private void execBackground_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    ExecutorProgressBar.Value += 1;
}

And in my Executor class :

private BackgroundWorker myExecutorWindow;

[...]

public void Run(object sender)
{
            myExecutorWindow = sender as BackgroundWorker;
            string[] options = new string[2];
            int i = 0;

            while (LeftToRun > 0)
            {
                if (CurrentRunningThreads < MaxThreadsRunning)
                {
                    BackgroundWorker myThread = new BackgroundWorker();
                    myThread.DoWork += new DoWorkEventHandler(backgroundWorkerRemoteProcess_DoWork);
                    myThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorkerRemoteProcess_RunWorkerCompleted);
                    myThread.ProgressChanged += new ProgressChangedEventHandler(backgroundWorkerRemoteProcess_ProgressChanged);
                    myThread.WorkerReportsProgress = true;
                    myThread.WorkerSupportsCancellation = true;

                    myThread.RunWorkerAsync(new string[2] {opt1, opt2});

                    CurrentRunningThreads++;
                    i++;
                    LeftToRun--;      
                }
            }

[...]

private void backgroundWorkerRemoteProcess_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker myBackgroundWorker = sender as BackgroundWorker;
            myBackgroundWorker.ReportProgress(1);
            // PROCESSING MY STUFF HERE
            myBackgroundWorker.ReportProgress(1);
        }

        private void backgroundWorkerRemoteProcess_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            myExecutorWindow.ReportProgress(1);
        }
ack__
  • 913
  • 1
  • 14
  • 39
0

I found a really simple solution to create a thread to run any block of code as well as handle Invocation back to the main thread to change the control's properties. It works out of the box with .NET 4.5 and the lambda call on the Dispatcher could be adapted to work with earlier versions of .NET. The main benefit is it's just so blissfully simple and perfect when you just need a quick thread for some really basic bit of code.

So presuming you have a progress bar somewhere on your dialog in scope do this:

progBar.Minimum = 0;
progBar.Maximum = theMaxValue;
progBar.Value = 0;

Dispatcher disp = Dispatcher.CurrentDispatcher;

new Thread(() => {
    // Code executing in other thread
    while (progBar.Value < theMaxValue)
    {
        // Your application logic here


        // Invoke Main Thread UI updates
        disp.Invoke(
            () =>
            {

                progBar.Value++;
            }
        );

    }
}).Start();

You also need to ensure you have a reference to WindowsBase.dll

If you want a more reusable snippet of code running as the thread start you could use a method as the delegate but I find the inline lambda so easy for simple tasks and you don't need to deal with events as with the Background Worker approaches.

Stephen York
  • 1,247
  • 1
  • 13
  • 42