Solved

C# Background task

Posted on 2011-02-17
31
1,144 Views
Last Modified: 2013-12-17
Hi,

I have a background task which is updating progress bar that are located in different window.
How can I do it?

Thanks,
JT
0
Comment
Question by:jtran007
  • 15
  • 6
  • 5
  • +3
31 Comments
 
LVL 29

Expert Comment

by:anarki_jimbel
ID: 34921746
There are couple of good tutorial on a background worker:

http://dotnetperls.com/progressbar
http://dotnetperls.com/backgroundworker

If you start a bg process in a one form but need to update a progressbar in another form - just pas reference for this progresbar from one form to onother

0
 
LVL 11

Expert Comment

by:Sudhakar Pulivarthi
ID: 34922207
0
 
LVL 7

Expert Comment

by:dimaj
ID: 34922664
You also have to test whether a control you are trying to update was created by the same thread otherwise you're gong to get exceptions...

This link shows how to deal with this: http://elegantcode.com/2009/07/03/wpf-multithreading-using-the-backgroundworker-and-reporting-the-progress-to-the-ui/
0
DevOps Toolchain Recommendations

Read this Gartner Research Note and discover how your IT organization can automate and optimize DevOps processes using a toolchain architecture.

 

Author Comment

by:jtran007
ID: 34922762
Hi Anarki,

You said pass reference from one for to the other. I don't understand what you mean.
Suppose in Form1, I have progressBar, and backgroundworker1. While backgroundworker1
is still running in the background, I switch to Form2 which has progressBar2. How backgroundworker1
know to update progressBar2. Please give me an example.

Thanks,
JT
0
 

Author Comment

by:jtran007
ID: 34922988
Hi,

Can one background task reports value to different progress bar locating in different forms?
If yes, how can it be done?

Thanks.
JT
0
 
LVL 7

Accepted Solution

by:
dimaj earned 250 total points
ID: 34923101
Sure it can.

You can create an event and fire it off every time something has happened. The other form should have a reference to the first form and subscribe to the event that first form is generating.

Your event should send data such as what is the current task progress. When second form receives your event, it will get the data out of it and modify the value of your progress bar.

Does this make sense?
0
 
LVL 29

Expert Comment

by:anarki_jimbel
ID: 34923140
OK, in the link I sent we have:

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Change the value of the ProgressBar to the BackgroundWorker progress.
            progressBar1.Value = e.ProgressPercentage;
            // Set the text.
            this.Text = e.ProgressPercentage.ToString();
        }

If you have a reference to a Form2 and it's ProgressBar2 (e.g.) - just do same!:

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // Change the value of the ProgressBar to the BackgroundWorker progress.
            progressBar1.Value = e.ProgressPercentage;
            // it's another story how you get this reference!
            form2Instance.progressBar2.Value = e.ProgressPercentage;
            // Set the text.
            this.Text = e.ProgressPercentage.ToString();
        }

The trick is to get this reference. It shouldn't be too hard. Or may be I misunderstand your task...
0
 
LVL 7

Expert Comment

by:dimaj
ID: 34923162
While your approach will work (assuming you face dealt with threading issues), it's a better practice to use events since your structure becomes more portable
0
 
LVL 3

Expert Comment

by:chandra_darbha
ID: 34923823
When updating UI elements / controls from a Background process you should use Control.Invoke.

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (progressBar1.InvokeRequired)
            {
                Action<ProgressChangedEventArgs> t = new Action<ProgressChangedEventArgs>(SetValues);
                progressBar1.Invoke(t, e);
            }
        }

        private void SetValues(ProgressChangedEventArgs e)
        {
            // Change the value of the ProgressBar to the BackgroundWorker progress.
            progressBar1.Text = e.ProgressPercentage;
            // Set the text.
            this.Text = e.ProgressPercentage.ToString();
        }

Thanks,
Chandra
0
 
LVL 3

Expert Comment

by:chandra_darbha
ID: 34923831
0
 

Author Comment

by:jtran007
ID: 34926162
Hi Anarchi,

The code  // it's another story how you get this reference!
            form2Instance.progressBar2.Value = e.ProgressPercentage;

I use form2.progressBar2.value , but it is not working.
Please help.
JT
0
 

Author Comment

by:jtran007
ID: 34927194
Hi,

From backgroundworker, how can I ask GUI switch to another form?

Thanks,
JT
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 34927374
@chandra_darbha...the BackgroundWorker() control events are ALREADY MARSHALED for you so using Invoke() in this case is redundant.

See: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

    "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."

Thus if you wanted to update the GUI directly from the DoWork() handler (or from a manually created thread) then you'd need to use Invoke()/Delegats.

***********************************************************************

@jtran007,

I assume that in Form1, you are displaying Form2 with something like this?

    private void Foo()
    {
        Form2 f2 = new Form2();
        f2.Show();
    }

The scope of "f2" is limited to the method in which it resides.  You could move the reference up to the CLASS (Form1) level so that it can be accessed in the ProgressChanged() event:

public partial class Form1 : Form
    {

        private Form2 f2; // keep a reference to our instance of Form2 so we can access it!

        private void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            f2 = new Form2();
            f2.Show();
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (f2 != null)
            {
                // Change the Modifiers() property of "progressBar1" on Form2 to Public:
                f2.progressBar1.Value = e.ProgressPercentage;
            }
        }
    }

Open in new window

0
 

Author Comment

by:jtran007
ID: 34932807
Hi chandra,

There is simpler solution:

In teh bwgProgressChanged(...)
{
 progressBar.value = e.percentage; // one line of code
}

Regards,
JT
0
 

Author Comment

by:jtran007
ID: 34932812
Hi idle,

Your suggestion is not working. I tried it and the reference to progressbar on other form
is invalid.

Thanks,
JT
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 34932845
How is it "invalid"?  
*You would have to change the Modifiers() Property of the ProgressBar to Public so it could be seen outside of the Form.

Otherwise I'm 100% sure the approach would work and the problem is either that your setup is different or you are trying to implement it incorrectly.

Show us more complete code if you still can't get it to work...

0
 

Author Comment

by:jtran007
ID: 34932857
Hi dimai,

I think your solution is the best. How can you priotize events, attached is the codes which show progress bar is always started first eventhough I check the text box before I fire the progressbar.

Program.cs
0
 

Author Comment

by:jtran007
ID: 34932858
HI dimai,

some mor files Form1.cs
0
 

Author Comment

by:jtran007
ID: 34932860
Hi dimai,

one more file, I don't know how to attacch more than one file.
Class1.cs
0
 
LVL 7

Expert Comment

by:dimaj
ID: 34934153
So, you were super close!

Here are the files
Class1.cs
Form1.cs
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 34935523
You still don't need the Invoke() code though as you're raising the Class1 event from the ProgressChanged() event which is already marshaled to the UI thread (since Class1 was created from the Form1 thread).
0
 
LVL 7

Expert Comment

by:dimaj
ID: 34935591
While you are right, I used it as an example in case he wants to use a different approach.
0
 

Author Comment

by:jtran007
ID: 34935705
Hi Idle,

Sorry you are right. I did not change its modifiers to public. However I 'll take dimai as the final
solution. Thanks,

Jt
0
 

Author Comment

by:jtran007
ID: 34935728
Hi ,

I run into problem now. Since I use events to pass info b/w forms, however one of the object which
creates a thread to collect data from serial port, and use eventst to pass data to my form.  Since the thread can't pass info to GUI form even using event. Is this correct?

Thanks all,
JT

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 34935741
The form can receive events on a different thread no problem.  If you need to update the GUI with that info, though, then use Invoke() with a Delegate as demonstrated by the others.

If you don't like using Invoke() then use the BackgroundWorker() instead of a manual thread.  You can also use a SynchronizationContext() instead of Invoke().
0
 

Author Comment

by:jtran007
ID: 34947592
Hi dimai,

Thanks. Your sample causes cross-thread operation:

   public void Foo()
        {
            // simply an example of raising the event:
            //this.Progress(25, "1/4 of the way there!");
            this.Message("1/4 of the way there!");
        }

and the cross-thread occurs here:
    void c1_Message(string msg)
        {
            textBox1.Text = msg;
        }

Since the foo happened in the backgroundworker (another thread) can't call  GUI control. Do I miss
something?

Thanks,
JT
0
 
LVL 7

Expert Comment

by:dimaj
ID: 34947656
Ohh!

I'm sorry... You have to follow the same structure as I used for c1_Progress...

if (textBox1.InvokeRequired) {
  this.Invoke(new MethodInvoker(delegate {textBox1.Text = msg; }));
}
else {
  textBox1.Text = msg;
}
0
 

Author Comment

by:jtran007
ID: 34947673
Hi dimai,

I found the meaning of your suggestion by changing the c1_Message as shown:

    void c1_Message(string msg)
        {
            if (textBox1.InvokeRequired)
            {
                this.Invoke(new MethodInvoker(delegate { textBox1.Text = msg; }));
            }
            else
            {
                textBox1.Text = msg;
            }
        }

It is working now. Thanks,
JT
0
 

Author Comment

by:jtran007
ID: 34947681
Hi idle..,

Is it possible you give a sample code using SynchronisationContext()?

Thanks,
JT
0
 
LVL 85

Assisted Solution

by:Mike Tomlinson
Mike Tomlinson earned 250 total points
ID: 34962231
Here's an example using a manual Thread and the SynchronizationContext:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            Class1 c1 = new Class1();
            c1.Progress += new Class1.ProgressDelegate(c1_Progress);
            c1.Complete += new Class1.CompleteDelegate(c1_Complete);
            c1.Start();
        }

        private void c1_Progress(int val)
        {
            // ... when we get here we are already on the main UI thread ...
            progressBar1.Value = val;
        }

        private void c1_Complete()
        {
            // ... when we get here we are already on the main UI thread ...
            button1.Enabled = true;
            MessageBox.Show("Done!");
        }

    }

    public class Class1
    {

        public event ProgressDelegate Progress;
        public delegate void ProgressDelegate(int val);

        public event CompleteDelegate Complete;
        public delegate void CompleteDelegate();

        private Thread T = null;
        private SynchronizationContext SC = null;

        public Class1()
        {
            this.SC = System.Windows.Forms.WindowsFormsSynchronizationContext.Current;
        }

        public void Start()
        {
            if (this.T == null)
            {
                this.T = new Thread(new ThreadStart(this.Worker));
                this.T.IsBackground = true;
                this.T.Start();
            }
        }

        private void Worker()
        {
            int max = 10;
            for (int i = 0; i <= max; i++)
            {
                System.Threading.Thread.Sleep(500);

                if (this.SC != null)
                {
                    int percentage = (int)((double)i / (double)max * (double)100);
                    this.SC.Post(new SendOrPostCallback(this.RaiseProgressEvent), percentage);
                }
            }

            if (this.SC != null)
            {
                this.SC.Post(new SendOrPostCallback(this.RaiseCompleteEvent), null);
            }
        }

        private void RaiseProgressEvent(Object o)
        {
            // ... when we get here we are already on the main UI thread ...
            this.Progress((int)o);
        }

        private void RaiseCompleteEvent(Object o)
        {
            // ... when we get here we are already on the main UI thread ...
            this.T = null;
            this.Complete();
        }

    }

}

Open in new window

0
 

Author Closing Comment

by:jtran007
ID: 34966641
Thanks,
JT
0

Featured Post

DevOps Toolchain Recommendations

Read this Gartner Research Note and discover how your IT organization can automate and optimize DevOps processes using a toolchain architecture.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Wouldn’t it be nice if you could test whether an element is contained in an array by using a Contains method just like the one available on List objects? Wouldn’t it be good if you could write code like this? (CODE) In .NET 3.5, this is possible…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
Along with being a a promotional video for my three-day Annielytics Dashboard Seminor, this Micro Tutorial is an intro to Google Analytics API data.
This video shows how to quickly and easily add an email signature for all users on Exchange 2016. The resulting signature is applied on a server level by Exchange Online. The email signature template has been downloaded from: www.mail-signatures…

777 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question