BackgroundWorker not firing RunWorkerCompleted - c#

The first time I run my backgroundworker it runs correctly - updates a datatable in the background and then RunWorkerCompleted sets the datatable as a datagridview datasource.
If I then run it again, the datagridview clears and doesn't update. I can't work out why.
I've verified that the datatable contains rows when my code hits dgvReadWrites.DataSource.
private void btnGenerateStats_Click(object sender, EventArgs e)
{
dtJobReadWrite.Columns.Clear();
dtJobReadWrite.Rows.Clear();
dgvReadWrites.DataSource = dtJobReadWrite;
List<Tuple<string, string>>jobs = new List<Tuple<string, string>>();
foreach (ListViewItem job in lstJobs.SelectedItems)
{
jobs.Add(new Tuple<string, string>(job.Text, job.SubItems[2].Text));
}
BackgroundWorker bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
bgw.WorkerSupportsCancellation = true;
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
pbarGenStats.Style = ProgressBarStyle.Marquee;
pbarGenStats.MarqueeAnimationSpeed = 30;
pbarGenStats.Visible = true;
bgw.RunWorkerAsync(jobs);
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgw = sender as BackgroundWorker;
List<Tuple<string, string>> jobs = (List<Tuple<string, string>>)e.Argument;
GetReadWriteStats(jobs);
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker bgw = sender as BackgroundWorker;
bgw.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.DoWork -= new DoWorkEventHandler(bgw_DoWork);
pbarGenStats.MarqueeAnimationSpeed = 0;
pbarGenStats.Value = 0;
pbarGenStats.Visible = false;
dgvReadWrites.DataSource = dtJobReadWrite;
dgvReadWrites.Visible = true;
dgvReadWrites.Refresh();
}

private void btnGenerateStats_Click(object sender, EventArgs e)
{
//...
dgvReadWrites.DataSource = dtJobReadWrite;
// etc...
}
That's a problem, you are updating dtJobReadWrite in the BGW. That causes the bound grid to get updated by the worker thread. Illegal, controls are not thread-safe and may only be updated from the thread that created them. This is normally checked, producing an InvalidOperationException while debugging but this check doesn't work for bound controls.
What goes wrong next is all over the place, you are lucky that you got a highly repeatable deadlock. The more common misbehavior is occasional painting artifacts and a deadlock only when you are not close. Fix:
dgvReadWrites.DataSource = null;
and rebinding the grid in the RunWorkerCompleted event handler, like you already do.

Because you unscubscribe from those events
bgw.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.DoWork -= new DoWorkEventHandler(bgw_DoWork);
Remove those lines

Why are you creating a new BackgroundWorker every time you want to run it? I would like to see what happens with this code if you use one instance of BackgroundWorker (GetReadWriteWorker or something along those lines), subscribe to the events only once, and then run that worker Async on btnGenerateStats_Click.

Related

Progress Bar not working properly in background worker

I need to have a progress bar to work(visible) at the time of the one process after that the visibility should be set to false. I am using a background worker for this process. but while using the visibility property the application is getting stalled other wise the application is running properly. I am using Devexpress progress bar .Please help me with this. This is the code which I am working.
private void Generate_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int a = 0;
int b = 0;
ProgressBar.Visible = true;
ProgressBar.Properties.Step = 1;
ProgressBar.Properties.PercentView = true;
ProgressBar.Properties.Maximum = SpecInformations.TotalSPCOCount;
ProgressBar.Properties.Minimum = 0;
Method_Call(a,b, sender as BackgroundWorker);
}
private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar.PerformStep();
ProgressBar.Update();
}
private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBar.Visible = false;
}
You cannot access Windows controls like ProgressBar from inside the DoWork method or any method it has called because the thread that runs this code is a background thread and not the same thread that created the Control. You will get an exception whose message states that the control is being access by a thread other than the thread that created it, if you try. This is an inviolable rule about windows controls; they must always only be accessed by the thread that created them
The BackgroundWorker has a WorkerReportsProgress property that must be set to true, and a ReportProgress() method that you can call with an int (and pass an optional object for more info) of the percentage complete. When you call this method in your DoWork, the BackgroundWorker will automatically raise the ProgressChanged event and critically, it does so using the foreground thread it was created with(the same thread your other controls were created with) so code inside your ProgressChanged event handler is run using the proper thread and can access the ProgressBar Control without causing an exception
In summary:
Set WorkerReportsProgress to true
Call ReportProgress inside DoWork, passing a percentage complete or using the int to indicate the process has reached some stage (it doesn't have to be a percentage)
Attach an event handler to your worker's ProgressChanged event
Move your ProgressBar code to the ProgressChanged event handler
the code can help you to use progress bar in BackgroundWorker:
private void cmdButton_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 101; i++)
{
worker.ReportProgress(i);
System.Threading.Thread.Sleep(1000);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblProgress.Text = ("Progress: " + e.ProgressPercentage.ToString() + "%");
}
for additional info can use the link

C# BackgroundWorker update UI from DoWork event

I know that the UI can only be updated from the main thread; but why can I update a StatusStrip Label within my DoWork event? but when I try to update another control, the program throws a cross thread exception (as expected).
And since this is possible, is it a good idea/practice update this label from within event?
Here's my code:
public Form1()
{
InitializeComponent();
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerCompleted += bw_WorkCompleted;
}
private void bw_DoWork(object sender, DoWorkEventArgs e) {
toolStripStatusLabel1.Text = "foo";
}

Exception when update a progressbar in a Background Worker

I created a wpf application.
In my application, i open a windows and i copy in background some files.
I would like to display and update a progressbar during thos copy.
I tried to use a BackgroundWorker :
public partial class FenetreProgressBar : Window
{
private readonly BackgroundWorker worker = new BackgroundWorker();
public FenetreProgressBar(ObservableCollection<Evenement.FichierJoint> CollectionFicJointsToAdd)
{
InitializeComponent();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.ProgressChanged +=worker_ProgressChanged;
worker.RunWorkerAsync(CollectionFicJointsToAdd);
}
private void ProgressChanged(double Persentage, ref bool Cancel)
{
if (Cancel)
this.Close();
worker.ReportProgress((int)Persentage);
}
private void Completedelegate()
{
this.Close();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
ObservableCollection<Evenement.FichierJoint> collection = e.Argument as ObservableCollection<Evenement.FichierJoint>;
//2) On ajoute les fichiers joints à l'évènements ( ce qui va donc les copier dans le répertoire paramétré)
foreach (Evenement.FichierJoint FichierJoint in collection)
{
if (FichierJoint.strPathFichier.Length > 0)
{
Evenement.FichierJoint monFichierJoint = new Evenement.FichierJoint(FichierJoint.strPathFichier, App.obj_myEvenement.strEvtNumeroString);
MaProgressBar.Minimum = 0;
MaProgressBar.Maximum = 100;
monFichierJoint.copyObject.OnProgressChanged += ProgressChanged;
monFichierJoint.copyObject.OnComplete += Completedelegate;
monFichierJoint.AddFichierJoint();
}
}
}
private void worker_RunWorkerCompleted(object sender,RunWorkerCompletedEventArgs e)
{
// Traitement terminé, on ferme la fenetre
this.Close();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
MaProgressBar.Value = e.ProgressPercentage;
}
}
When the programme go here :
MaProgressBar.Minimum = 0;
MaProgressBar.Maximum = 100;
I have an exception : " The calling thread cannot access this object because a different thread owns it".
I have read severals answer on google and stackoverflow but i don't try to use this approach with the BackgroundWorker.
Anyone coule help me in order to avoid this exception and solve the problem please ?
Thanks a lot,
Best regards :)
You cannot modify UI objects from background worker. You need to invoke the methods on UI dispatcher like this -
App.Current.Dispatcher.Invoke((Action)delegate
{
MaProgressBar.Minimum = 0;
MaProgressBar.Maximum = 100;
});
But since you only need to set maximum an minimum values, i would suggest you to set these values outside of backgroundWorker in constructor -
public FenetreProgressBar(ObservableCollection<Evenement.FichierJoint>
CollectionFicJointsToAdd)
{
InitializeComponent();
MaProgressBar.Minimum = 0;
MaProgressBar.Maximum = 100;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.ProgressChanged +=worker_ProgressChanged;
worker.RunWorkerAsync(CollectionFicJointsToAdd);
}
Why don't you use C# new async and await new feature/keywords? they were specifically designed to make the UI responsive while doing lengthy operations.
You should try this:
_backgroundWorker.OnProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
and then
// Event handler
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update main thread here
}

Linq to Sql result in combobox async

I'm developing a winform application and would make a request in the database and populate my combobox asynchronously but am having problem of access control because they come from another thread, here's the code.
this.backWorker.DoWork + = delegate
{
comboBoxUsers.DataSource = repositoryUser.SelectAll();
comboBoxUsers.ValueMember = "UserId";
comboBoxUsers.DisplayMember = "Name";
};
backWorker.RunWorkerAsync ();
I am studying about envoke but I'm having trouble getting to implement this,
I needed to do was leave the DoWork event visible progress bar and select to do this.
Only query your repository on the BackgroundWorker and return the results through the ProgressChangedEvenHandler to the UI
//Set the ComboBox Properties on the Form, not in the worker.
comboBoxUsers.ValueMember = "UserId";
comboBoxUsers.DisplayMember = "Name";
BackgroundWorker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.WorkerReportsProgress = true;
worker.ProgressChanged += new ProgressChangedEventHandler(Worker_ProgressChanged);
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgrounderWorker worker = (BackgroundWorker)sender;
//Query the database
//Instantiate a custom-class to contain the results
IList<Users> users = userRepository.SelectAll();
QueryResults results = new QueryResults(users);
worker.ReportProgress(0, results);
}
//Back In the UI Layer
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var result = (QueryResult)e.UserState;
comboBoxUsers.DataSource = result.Users;
}
your delegate should be written like this:
this.backWorker.DoWork += delegate(object s, DoWorkEventArgs args)
{
//...
}
for details on how to access UI controls of the UI Thread from another thread see here:
Access windows control from Backgroundworker DoWork
there is a clear answer at that link, here a snippet:
this.Invoke(new MethodInvoker(delegate {
// This code executes on the GUI thread.
}));

Fill dataGridView thank's to backGroundWorker

I have this code snippet:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
remplirDataGrid();
}
private void frmChercherActesLoad(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void remplirDataGrid()
{
dataGridView1.DataSource = ActeServices.getAllActes(0, 40);
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].HeaderText = "Code acte";
dataGridView1.Columns[2].HeaderText = "Désignation";
dataGridView1.Columns[3].HeaderText = "Pris en charge";
dataGridView1.Columns[4].HeaderText = "Id article";
dataGridView1.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
}
And here is the method getAllActe:
public static IEnumerable<Acte> getAllActes(int skipCount, int takeCount)
{
var myTableAdapter = new SmartDocDALServices.SmartDocDataSetTableAdapters.actesTableAdapter();
myTableAdapter.Fill(myDataSet.actes);
var myResult = from q in myDataSet.actes.AsEnumerable()
select new Acte
{
code = q.code,
designation = q.designation,
priseEnCharge = q.prise_en_charge,
idArticle = q.id_article,
};
if (skipCount != -1)
myResult.Skip(skipCount);
if (takeCount != -1)
myResult.Take(takeCount);
IEnumerable<Acte> myResultRet = myResult.ToList();
return myResultRet;
What I like to do is to fill my datagridview using the background worker once I run the application I got this error:
Inter-thread operation not valid: Control 'dataGridView1 has been the subject of an access from a thread other than the one it was created.
I try this
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
IEnumerable<Acte> result = ActeServices.getAllActes(0, 40);
backgroundWorker1.ReportProgress(0, result);
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
dataGridView1.DataSource = (IEnumerable<Acte>)e.UserState;
dataGridView1.Columns[0].Visible = false;
dataGridView1.Columns[1].HeaderText = "Code acte";
***
but nothing gained in time?. I'd like that the datagrid update when the BGW loads data foreash data load it add it to the DGV
You can't update the UI from a BackgroundWorker thread.
You need to send an event to the UI and then have something like:
private void EventHandler(object sender, YourEventArgs e)
{
if (this.dataGridView1.InvokeRequired)
{
this.dataGridView1.Invoke((MethodInvoker)delegate { this.AddToGrid(e.YourData); });
}
else
{
this.AddToGrid(e.YourData);
}
}
The DataGridView is not thread safe.
However, setting the DataSource if the data is already available should be fast enough.
I would recommend:
Only use your BackgroundWorker to load the data in another thread
Set the DataSource and the other modifications of the datagridview in the RunWorkerCompleted Event (you can pass the result from the DoWork method to the Completed event by setting
e.Result = ActeServices.getAllActes(0, 40);
Optional: Set dataGridView1.AutoGenerateColumns to false and manually add the columns either in the Windows Forms Designer or in code to avoid flicker.
It is because GUI stuff cannot be modified from other threads than the GUI thread. To fix this, you need to invoke the changes on the GUI thread by using the Dispatcher.
The DataGrid should be setup beforehand, so all you do in your async operation is fill the data.
var data = ActeServices.getAllActes(0, 40);
Dispatcher.BeginInvoke( new Action( () => { dataGridView1.DataSource = data; }))
The BackgroundWorker class was designed to run a long-standing operation on a background thread. Since you are only allowed to access UI components from the thread that created them, you can use the RunWorkerCompleted event of the BackgroundWorker class to update your UI once your DoWork handler has completed. Also, you can safely update a progress UI using the ProgressChanged event of the BackgroundWorker class.

Categories

Resources