I have created a GUI (winforms) and added a backgroundworker to run in a separate thread.
The backgroundworker needs to update 2 labels continuously.
The backgroundworker thread should start with button1 click and run forever.
class EcuData
{
public int RPM { get; set; }
public int MAP { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
EcuData data = new EcuData
{
RPM = 0,
MAP = 0
};
BackWorker1.RunWorkerAsync(data);
}
private void BackWorker1_DoWork(object sender, DoWorkEventArgs e)
{
EcuData argumentData = e.Argument as EcuData;
int x = 0;
while (x<=10)
{
//
// Code for reading in data from hardware.
//
argumentData.RPM = x; //x is for testing only!
argumentData.MAP = x * 2; //x is for testing only!
e.Result = argumentData;
Thread.Sleep(100);
x++;
}
private void BackWorker1_RunWorkerCompleted_1(object sender, RunWorkerCompletedEventArgs e)
{
EcuData data = e.Result as EcuData;
label1.Text = data.RPM.ToString();
label2.Text = data.MAP.ToString();
}
}
The above code just updated the GUI when backgroundworker is done with his job, and that's not what I'm looking for.
You need to look at BackgroundWorker.ReportProgess.
You can use it to periodically pass back an object to a method in the main thread, which can update the labels for you.
You can use a System.Threading.Timer and update the UI from the Timer's callback method by calling BeginInvoke on the main Form.
uiUpdateTimer = new System.Threading.Timer(new TimerCallback(
UpdateUI), null, 200, 200);
private void UpdateUI(object state)
{
this.BeginInvoke(new MethodInvoker(UpdateUI));
}
private void UpdateUI()
{
// modify labels here
}
class EcuData
{
public int RPM { get; set; }
public int MAP { get; set; }
}
private void button1_Click(object sender, EventArgs e)
{
EcuData data = new EcuData
{
RPM = 0,
MAP = 0
};
BackWorker1.RunWorkerAsync(data);
}
private void BackWorker1_DoWork(object sender, DoWorkEventArgs e)
{
EcuData argumentData = e.Argument as EcuData;
int x = 0;
while (x<=10)
{
e.Result = argumentData;
Thread.Sleep(100);
this.Invoke((MethodInvoker)delegate
{
label1.Text = Convert.ToString(argumentData.RPM = x); //send hardware data later instead, x is for testing only!
label2.Text = Convert.ToString(argumentData.MAP = x * 2); //send hardware data later instead, x is for testing only!
});
x++;
}
}
This works, but it is the correct way of doing it?
Related
It seems I need to use a timer in a separate thread, since a time is not working if i use a normal time. So i did the following, however while it stops at the Invoke statement the progress bar does not change value!
public partial class LoadingForm : Form
{
System.Threading.Timer t;
public LoadingForm()
{
InitializeComponent();
}
private void LoadingForm_Load(object sender, EventArgs e)
{
progressBar.Value = 0;
progressBar.Minimum = 0;
progressBar.Maximum = 100;
t = new System.Threading.Timer(TimerCallback, null, 0, 500);
}
private void TimerCallback(object e)
{
//time is here
progressBar.Invoke(new Action(() => progressBar.Value++));
}
private void timerTick()
{
//this is what i need to do !!!!
if (progressBar.Value == progressBar.Maximum) { progressBar.Value = 0; }
progressBar.Value++;
}
}
(if it works ideally I would want to be able to call timerTick() in the Invoke statement)
I am copying large files from one place to another.
It is taking a long time so I decided to use a progress bar.
I am following this example.
The copyItems() function iterates through the list items and copies the items from another place. It in turn calls a function CopyListItem which copies one item .
I need to tie the backgroundWorker1.ReportProgress(i) to the total no of items i.e. itemcoll.
I do not want to use thread.sleep() .
The progress bar needs to show the actual time required to copy the file from one place to another.
The Progress bar needs to progress when only when one file is copied.
IT needs to complete when all the files are copied
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, System.EventArgs e)
{
// Start the BackgroundWorker.
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= itemscoll.count; i++)
{
// Wait 100 milliseconds.
Thread.Sleep(100);
// Report progress.
backgroundWorker1.ReportProgress(i);
}
}
private void CopyListItem(SPListItem sourceItem, string destinationListName, string destServerURL)
{
// copy items
}
private void copyitems()
{
try
{
int createdYear = 0;
backgroundWorker1.RunWorkerAsync();
foreach (SPListItem sourceItem in itemscoll)
{
if (Helper.year == createdYear)
{
CopyListItem(sourceItem, Helper.destinationListName,Helper.destServerURL);
DeleteItem(CompRefNo);
}
}
}
catch()
{}
}
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();
}
}
}
You need to do your copy-action in the DoWork-Event of the BackgroundWorker. So when you call the backgroundWorker1.RunWorkerAsync() you'll have to pass an object to it containing all the needed informations. This could be something like:
internal class WorkerItem
{
public WorkerItem(List<SPListItem> spListItems, string destinationListName, string destinationServerURL)
{
SPListItems = new List<SPListItem>(spListItems);
DestinationListName = destinationListName;
DestinationServerURL = destinationServerURL;
}
public List<SPListItem> SPListItems { get; private set; }
public string DestinationListName { get; private set; }
public string DestinationServerURL { get; private set; }
}
The call of RunWorkerAsync can look something like:
backgroundWorker1.RunWorkerAsync(new WorkerItem(...));
In your DoWork-Handler you than have to get this object from the e.Argument and cast it to WorkerItem. Than you can work with it like:
private void BackgroundWorker1OnDoWork(object sender, DoWorkEventArgs e)
{
WorkerItem workerItem = (WorkerItem)e.Argument;
for (int i = 0; i < workerItem.SPListItems.Count(); i++)
{
// CopyListItem is doing the copy for one item.
CopyListItem(workerItem.SPListItems[i], workerItem.DestinationListName, workerItem.DestinationServerURL);
((BackgroundWorker)sender).ReportProgress(i + 1);
}
}
The ProgressChanged-Handler only increments the value of the ProgressBar:
private void BackgroundWorker1OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
If you want to have more informations in the ProgressChanged-Handler you can pass an additional object as UserState. This you can get by object additional = e.UserState;
Right before you call backgroundWorker1.RunWorkerAsync(new WorkerItem(...)) you should set the Maximum of progressBar1 to the amount of SPListItems like:
progressBar1.Maximum = itemscoll.Count;
Your BackgroundWorker only reports it's progress to you if you set.
backgroundWorker1.WorkerReportsProgress = true;
If you want to be informed when the BackgroundWorker is finished you can get the RunWorkerCompleted-Event.
backgroundWorker1.RunWorkerCompleted += BackgroundWorker1OnRunWorkerCompleted;
Currently im trying to update my progress bar if the background worker reports something, heres my code
Form1.cs
namespace YTD
{
public partial class Form1 : Form
{
private Main app;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
int n;
bool isNumeric = int.TryParse(numberBox.Text, out n);
if (!String.IsNullOrWhiteSpace(emailBox.Text) && !String.IsNullOrWhiteSpace(passBox.Text) && !String.IsNullOrWhiteSpace(numberBox.Text) && isNumeric)
{
this.app = new Main(emailBox.Text, passBox.Text, n, logBox, statusBar, backgroundWorker1);
this.app.startMule();
}
else
{
MessageBox.Show("Please fill out all the form fields", "MuleMaker error");
}
}
}
}
And my Main.cs
namespace YTD.classes
{
public class Main
{
private String email;
private String password;
private int number;
private RichTextBox logBox;
private ProgressBar statusBar;
private BackgroundWorker threadWorker;
public Main(String email, String password, int number, RichTextBox logBox, ProgressBar statusBar, BackgroundWorker threadWorker)
{
// Define some variables
this.email = email;
this.password = password;
this.number = number;
this.logBox = logBox;
this.statusBar = statusBar;
this.threadWorker = threadWorker;
}
public void startMule()
{
// Set progressbar 100% value
statusBar.Maximum = this.number;
if (!threadWorker.IsBusy)
{
threadWorker.RunWorkerAsync();
}
}
private void threadWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 10; i++)
{
// Perform a time consuming operation and report progress.
MessageBox.Show("ye");
System.Threading.Thread.Sleep(500);
threadWorker.ReportProgress(i * 10);
}
}
private void threadWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
statusBar.Increment(1);
}
}
}
Currently I get no errors but the progress bar value is not beeing changed.
Without the background worker i can update my progress bar fine but not while doing an expensive action.
Your posted Code does not reveal, if you registered your functions to the BackgroundWorker Events.
Creating a new BackgrounWorker isn't enough.
Here is an example:
public Class Main
{
public Main( ... )
{
BackgroundWorker worker = new BackgroundWorker()
worker.WorkerReportsProgress = true;
// Register to BackgroundWorker-Events
worker.DoWork += threadWorker_DoWork;
worker.ProgressChanged += threadWorker_ProgressChanged;
}
}
in addition you should tell your ProgressBar to rerender.
private void threadWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
statusBar.Increment(1);
statusBar.Invalidate(true);
}
at least you might want to use the value you have set calling ReportProgress(i * 10).
private void threadWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
statusBar.Value = e.ProgressPercentage;
statusBar.Invalidate(true);
}
In Form1 I removed/deleted the _busy variable. In Form1 top I did:
BackgroundWebCrawling bgwc;
Then in the button4 pause click event I did:
private void button4_Click(object sender, EventArgs e)
{
bgwc.PauseWorker();
label6.Text = "Process Paused";
button5.Enabled = true;
button4.Enabled = false;
}
In the button5 click event button I did:
private void button5_Click(object sender, EventArgs e)
{
bgwc.ContinueWorker();
label6.Text = "Process Resumed";
button4.Enabled = true;
button5.Enabled = false;
}
And the cancel button click event:
private void button3_Click(object sender, EventArgs e)
{
bgwc.CancelWorker();
cancel = true;
}
Then I'm checking in Form1 completed event if cancel is true or not:
if (cancel == true)
{
label6.Text = "Process Cancelled";
}
else
{
label6.Text = "Process Completed";
}
And this is how the BackgroundWebCrawling class look like now:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HtmlAgilityPack;
using System.Net;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
namespace GatherLinks
{
class BackgroundWebCrawling
{
public string f;
int counter = 0;
List<string> WebSitesToCrawl;
int MaxSimultaneousThreads;
public BackgroundWorker mainBackGroundWorker;
BackgroundWorker secondryBackGroundWorker;
WebcrawlerConfiguration webcrawlerCFG;
List<WebCrawler> webcrawlers;
int maxlevels;
public event EventHandler<BackgroundWebCrawlingProgressEventHandler> ProgressEvent;
ManualResetEvent _busy = new ManualResetEvent(true);
public BackgroundWebCrawling()
{
webcrawlers = new List<WebCrawler>();
mainBackGroundWorker = new BackgroundWorker();
mainBackGroundWorker.WorkerSupportsCancellation = true;
mainBackGroundWorker.DoWork += mainBackGroundWorker_DoWork;
}
private void mainBackGroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < WebSitesToCrawl.Count; i++)
{
_busy.WaitOne();
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
while (counter >= MaxSimultaneousThreads)
{
Thread.Sleep(10);
}
WebCrawler wc = new WebCrawler(webcrawlerCFG);
webcrawlers.Add(wc);
counter++;
secondryBackGroundWorker = new BackgroundWorker();
secondryBackGroundWorker.DoWork += secondryBackGroundWorker_DoWork;
object[] args = new object[] { wc, WebSitesToCrawl[i] };
secondryBackGroundWorker.RunWorkerAsync(args);
}
while (counter > 0)
{
Thread.Sleep(10);
}
}
private void secondryBackGroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
object[] args = (object[])e.Argument;
WebCrawler wc = (WebCrawler)args[0];
string mainUrl = (string)args[1];
wc.ProgressEvent += new EventHandler<WebCrawler.WebCrawlerProgressEventHandler>(x_ProgressEvent);
wc.webCrawler(mainUrl, maxlevels);
counter--;
}
public void Start(List<string> sitestocrawl, int threadsNumber, int maxlevels, WebcrawlerConfiguration wccfg)
{
this.maxlevels = maxlevels;
webcrawlerCFG = wccfg;
WebSitesToCrawl = sitestocrawl;
MaxSimultaneousThreads = threadsNumber;
mainBackGroundWorker.RunWorkerAsync();
}
private void x_ProgressEvent(object sender, WebCrawler.WebCrawlerProgressEventHandler e)
{
// OK .. so now you get the data here in e
// and here you should call the event to form1
Object[] temp_arr = new Object[8];
temp_arr[0] = e.csFiles;
temp_arr[1] = e.mainUrl;
temp_arr[2] = e.levels;
temp_arr[3] = e.currentCrawlingSite;
temp_arr[4] = e.sitesToCrawl;
temp_arr[5] = e.done;
temp_arr[6] = e.failedUrls;
temp_arr[7] = e.failed;
OnProgressEvent(temp_arr); /// Send the data + additional data from this class to Form1..
///
/*
* temp_arr[0] = csFiles;
temp_arr[1] = mainUrl;
temp_arr[2] = levels;
temp_arr[3] = currentCrawlingSite;
temp_arr[4] = sitesToCrawl;*/
}
private void GetLists(List<string> allWebSites)
{
}
public class BackgroundWebCrawlingProgressEventHandler : EventArgs
{
public List<string> csFiles { get; set; }
public string mainUrl { get; set; }
public int levels { get; set; }
public List<string> currentCrawlingSite { get; set; }
public List<string> sitesToCrawl { get; set; }
public bool done { get; set; }
public int failedUrls { get; set; }
public bool failed { get; set; }
}
protected void OnProgressEvent(Object[] some_params) // Probably you need to some vars here to...
{
// some_params to put in evenetArgs..
if (ProgressEvent != null)
ProgressEvent(this,
new BackgroundWebCrawlingProgressEventHandler()
{
csFiles = (List<string>)some_params[0],
mainUrl = (string)some_params[1],
levels = (int)some_params[2],
currentCrawlingSite = (List<string>)some_params[3],
sitesToCrawl = (List<string>)some_params[4],
done = (bool)some_params[5],
failedUrls = (int)some_params[6],
failed = (bool)some_params[7]
});
}
public void PauseWorker()
{
if (mainBackGroundWorker.IsBusy)
{
_busy.Reset();
}
}
public void ContinueWorker()
{
_busy.Set();
}
public void CancelWorker()
{
ContinueWorker();
mainBackGroundWorker.CancelAsync();
}
}
}
So I added the methods the pause the continue the cancel. In the dowork event, I changed all the things and added things.
But when I click the buttons there is no effect. Not pausing, not continue and not cancel. Nothing.
You never check the _busy status in mainBackGroundWorker_DoWork method;
for (int i = 0; i < WebSitesToCrawl.Count; i++)
{
_busy.WaitOne();
//...
}
also you should have your ManualResetEvent _busy in class with BackgroundWorker
ManualResetEvent _busy = new ManualResetEvent(true);
public BackgroundWorker mainBackGroundWorker;
public void PauseWorker()
{
if(mainBackGroundWorker.IsBusy)
{
_busy.Reset();
}
}
public void ContinueWorker()
{
_busy.Set();
}
and in Form1:
private void button4_Click(object sender, EventArgs e)
{
bgwc.PauseWorker();
//...
}
private void button5_Click(object sender, EventArgs e)
{
bgwc.ContinueWorker();
//...
}
to cancel the BackgroundWorker you can use CancellationPending property and CancelAsync method. Note: you should first unpause the worker.
public void CancelWorker()
{
ContinueWorker();
mainBackGroundWorker.CancelAsync();
}
private void mainBackGroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < WebSitesToCrawl.Count; i++)
{
_busy.WaitOne();
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
//...
}
}
If this doesn't help you, then you have problems with mainBackGroundWorker code and secondryBackGroundWorker.
This code only pauses mainBackGroundWorker, but not secondryBackGroundWorkers. The same with cancelation. If main worker is canceled? it will wait for all the secondary workers to finish their jobs. Also if you pause main worker? you can still have new results arriving from secondary workers.
You do not handle errors. If you have an exception in second worker, than you do not get any notification about that and also your main worker will never stop, because counter will never be 0.
There can be another problems, witch cause this behaviour.
I have a GridControl which I populate using a BackgroundWorker. Then I'm using another BackgroundWorker to perform some calculations on the dataset which is the datasource of the GridControl. As I'm trying to do this a cross thread operation on the GridControl error is thrown. I'm unable to understand that despite not performaing any operation on the gridcontrol itself how the error is generating. (I'm using DevExpress, but that should not change the concept).
Also is there any way I can use one BackgroundWorker to do different work, i.e. make this code more efficient.
Here is my code:-
public partial class MainForm : XtraForm
{
private BackgroundWorker loadworker = new BackgroundWorker();
private BackgroundWorker calcworker = new BackgroundWorker();
private AutoResetEvent resetEvent = new AutoResetEvent(false);
private Database _db = EnterpriseLibraryContainer.Current.GetInstance<Database>("ConnString");
private DataSet ds;
public MainForm()
{
InitializeComponent();
loadworker.DoWork += loadworker_DoWork;
loadworker.RunWorkerCompleted += loadworker_RunWorkerCompleted;
loadworker.ProgressChanged += loadworker_ProgressChanged;
loadworker.WorkerReportsProgress = true;
calcworker.DoWork += calcworker_DoWork;
calcworker.RunWorkerCompleted += calcworker_RunWorkerCompleted;
calcworker.ProgressChanged += calcworker_ProgressChanged;
calcworker.WorkerReportsProgress = true;
}
private void calcworker_DoWork(object sender, DoWorkEventArgs e)
{
int _cnt = 0;
foreach (DataRow dr in ds.Tables[0].Rows)
{
dr["GROSS"] = (decimal)dr["BASIC"] + (decimal)dr["HRA"] + (decimal)dr["DA"];
_cnt += 1;
}
for (int i = 0; i <= _cnt; i++)
{
Thread.Sleep(100);
calcworker.ReportProgress((100 * i) / _cnt);
}
}
private void calcworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.SetState(true);
this.MainInit();
}
private void calcworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.pgb_DataProgress.Position = e.ProgressPercentage;
}
private void loadworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.pgb_DataProgress.Position = e.ProgressPercentage;
}
private void loadworker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
DbCommand _cmd = _db.GetSqlStringCommand("SELECT Z.EMP_CODE,Z.BASIC,Z.DA,Z.HRA,CAST(0 AS DECIMAL) GROSS FROM Z000000001 Z");
DataSet _data = _db.ExecuteDataSet(_cmd);
for (int i = 0; i <= 10; i++)
{
Thread.Sleep(500);
loadworker.ReportProgress((100 * i) / 10);
}
e.Result = _data;
}
catch (Exception ex)
{
e.Cancel = true;
}
}
private void loadworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.ds = (DataSet)e.Result;
this.gridControl1.DataSource = ds.Tables[0];
this.SetState(true);
this.MainInit();
}
private void btn_FetchData_Click(object sender, EventArgs e)
{
this.gridControl1.DataSource = null;
this.SetState(false);
loadworker.RunWorkerAsync();
}
private void SetState(bool _state)
{
this.btn_Calculate.Enabled = _state;
this.btn_ClearGrid.Enabled = _state;
this.btn_FetchData.Enabled = _state;
}
private void MainInit()
{
this.pgb_DataProgress.Position = 0;
}
private void btn_ClearGrid_Click(object sender, EventArgs e)
{
this.gridControl1.DataSource = null;
}
private void btn_Calculate_Click(object sender, EventArgs e)
{
if (this.gridControl1.DataSource == null)
{
DevExpress.XtraEditors.XtraMessageBox.Show("Data Not loaded", "Message");
return;
}
else
{
this.SetState(false);
calcworker.RunWorkerAsync();
}
}
}
After you attached the Table as DataSource it belongs to the GUI. Suppose your user alters/deletes a row while your Calc thread is running. All sorts of race conditions might happen.
In short, you cannot access controls on a thread other than UI thread on which they are created. So any control method/property call has to be marshall on the UI thread using Control.Invoke method.
For example, in your case loadworker_RunWorkerCompleted event handler will be invoked on a worker thread and accessing control property will throw an error. You need to modify event handler as
private void loadworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
System.Action a = () => {
this.ds = (DataSet)e.Result;
this.gridControl1.DataSource = ds.Tables[0];
this.SetState(true);
this.MainInit();
};
this.gridControl1.Invoke(a);
}