Why BackgroundWorker always is busy? - c#

I realized something strange in my background worker in my WPF application.
What I'm trying to accomplish right now is to wait until the BW finishes to start another thread.
Check the following code:
if (bw.IsBusy)
{
bw.CancelAsync();
System.Threading.ThreadStart WaitThread =
new System.Threading.ThreadStart(delegate() {
while (bw.IsBusy)
{
System.Threading.Thread.Sleep(100);
}
bw.RunWorkerAsync();
});
System.Windows.Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
WaitThread); // if I remove this line, bw fires RunWorkerAsyncEvent
}
else
{
bw.RunWorkerAsync();
}
Please note that I added a Dispatcher.Invoke to wait until the bw is not busy, but ALL THE TIME IS BUSY if I Invoke it and never fires the RunWorkerAsyncCompleted event. Although,, if I remove that line, FIRES the event and it's really strange that.
How can I wait until the bw finishes?

This is called "deadlock", a very common threading problem. The BGW cannot stop being busy until the RunWorkerCompleted event runs. That event runs on your app's main thread, it can only run when your main thread isn't busy doing something else. It has to be idle, running inside the dispatcher loop.
But your main thread isn't idle, it is stuck inside the while() loop waiting for IsBusy to return false. So the event can never run since the main thread is busy waiting for the BGW to complete, the BGW cannot complete because the main thread never goes idle. "Deadly embrace", aka deadlock.
You will have to do this differently, you cannot wait. Say by creating another instance of BGW. Or moving the code after the wait into the RunWorkerCompleted event handler. If it is activated by, say, a button's Click event then be sure to disable the button when call RunWorkerAsync(), re-enable it in RunWorkerCompleted.

The deadlock occurs due to the "main" thread of the application can not run (and handle events), thus the Backgroundworker is never able to fire it's finished-function. What you can do to allow the application to fire this is add an Application.DoEvents();
This is what I currently use in a similiar scenario, and it appears to work like a charm!
while (bw.IsBusy)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
}

I'm not sure if I understood correctly, but I think you're are trying to restart the BackgroundWorker (so if it's busy, you stop it first and then you start it again), if this is what you want, try this:
declare this delegate somewhere in your class:
private delegate bool StateChecker();
and use this code to restart your BackgroundWorker:
StateChecker stillWorking = () => { return bw.IsBusy; };
if (bw.IsBusy)
{
bw.CancelAsync();
while ((bool)this.Dispatcher.Invoke(stillWorking, null)) Thread.Sleep(15);
}
bw.RunWorkerAsync();

Here is full and working code.
C# (window's class)
public partial class ForceSyncWindow : Window
{
BackgroundWorker backgroundWorker = new BackgroundWorker();
public ForceSyncWindow()
{
InitializeComponent();
ProgressBar1.Visibility = System.Windows.Visibility.Hidden;
backgroundWorker.WorkerSupportsCancellation = true;
// To report progress from the background worker we need to set this property
backgroundWorker.WorkerReportsProgress = true;
// This event will be raised on the worker thread when the worker starts
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
// This event will be raised when we call ReportProgress
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
ProgressBar1.Value = 0;
// TODO LOG = "Canceled";
}
else
{
// Finally, handle the case where the operation
// succeeded.
// TODO LOG e.Result.ToString();
}
ProgressBar1.Value = 0;
ProgressBar1.Visibility = System.Windows.Visibility.Hidden;
// Enable the Synchronize button.
this.Synchronize.IsEnabled = true;
// Disable the Cancel button.
this.Cancel.IsEnabled = false;
}
// On worker thread so do our thing!
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Your background task goes here
for (int i = 0; i <= 100; i++)
{
if (backgroundWorker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
// Report progress to 'UI' thread
backgroundWorker.ReportProgress(i);
// Simulate long task
System.Threading.Thread.Sleep(100); ;
}
}
}
// Back on the 'UI' thread so we can update the progress bar
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// The progress percentage is a property of e
ProgressBar1.Value = e.ProgressPercentage;
}
private void Synchronize_Click(object sender, RoutedEventArgs e)
{
ProgressBar1.Value = 0;
ProgressBar1.Visibility = System.Windows.Visibility.Visible;
// Disable the Synchronize button.
this.Synchronize.IsEnabled = false;
// Enable the Cancel button.
this.Cancel.IsEnabled = true;
// Start the background worker
backgroundWorker.RunWorkerAsync();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if (backgroundWorker.IsBusy)
{
// Cancel the background worker
backgroundWorker.CancelAsync();
}
}
}
XAML
<Window x:Class="MySyncManager.Views.ForceSyncWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ForceSyncWindow" Height="300" Width="509" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<Button Content="Synchronize" Name="Synchronize" HorizontalAlignment="Left" Margin="411,10,0,0" VerticalAlignment="Top" Width="75" Click="Synchronize_Click"/>
<RichTextBox HorizontalAlignment="Left" Height="132" Margin="10,116,0,0" VerticalAlignment="Top" Width="476">
</RichTextBox>
<Button Content="Cancel" x:Name="Cancel" HorizontalAlignment="Left" Margin="411,40,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.508,2.154" Click="Cancel_Click"/>
<ProgressBar Name="ProgressBar1" HorizontalAlignment="Left" Height="10" Margin="10,101,0,0" VerticalAlignment="Top" Width="476"/>
</Grid>
</Window>

Put it inside of another thread:
private void myButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker mainWorker = new BackgroundWorker();
mainWorker.DoWork += new DoWorkEventHandler(mainWorker_DoWork);
mainWorker.RunWorkerAsync();
}
void mainWorker_DoWork(object sender, DoWorkEventArgs e)
{
for(int x = 0; x >= 100; x++)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync(argument: x);
while(worker.IsBusy)
{
Thread.Sleep(100);
}
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
string iam = "Hello i'm thread number: " + e.Argument.ToString();
//do something ...
}

Related

Two issues with backgroundworker with progress bar WPF

I'm using WPF and I have main thread which is GUI (wizard).
When user click Finish on wizard it open second thread which display user progress bar used in background worker.
In Main thread I doing:
MessageWithProgressBar progress = new MessageWithProgressBar();
progress.Show();
createFilesInA();
createFilesInB();
createFilesInC();
createFilesInD();
createFilesInE();
createFilesInF();
createFilesInG();
createFilesInH();
createFilesInI();
createFilesInJ();
createFilesInK();
In each createFiles method I increment by 1 the static variable called currentStep which I used it in background worker as detailed below.
In background worker I doing:
public partial class MessageWithProgressBar : Window
{
private BackgroundWorker backgroundWorker = new BackgroundWorker();
public MessageWithProgressBar()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += ProgressChanged;
backgroundWorker.DoWork += DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(100);
int i = GeneralProperties.General.currentStep;
if (i > GeneralProperties.General.thresholdStep)
{
progress.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(delegate
{
progress.Value = 100;
title.Content = progress.Value.ToString();
return null;
}), null);
return;
}
else
{
progress.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(delegate
{
progress.Value = (int)Math.Floor((decimal)(8 * i));
progressLabel.Text = progress.Value.ToString();
return null;
}), null);
}
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progress.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(delegate
{
progress.Value = e.ProgressPercentage;
return null;
}), null);
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progress.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new DispatcherOperationCallback(delegate
{
progress.Value = 100;
title.Content = progress.Value.ToString();
return null;
}), null);
WindowMsgGenDB msg = new WindowMsgGenDB();
msg.Show();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (backgroundWorker.IsBusy == false)
{
backgroundWorker.RunWorkerAsync();
}
}
}
The main thread updated variable called currentStep and the second thread used it to report on the main thread progress.
The operations of the main thread takes a few seconds (not more 15 seconds)
I have two issues:
I see on progress bar only when currentStep=2 (then the progress is 16) and then the progress is 100, and I don't see every step
At the beginning, the progress bar is freeze and it seems like it stuck.
(maybe it connects to the call progress.Show() from the main thread?)
Thanks!
As far as I understand your code your background worker is not doing anything, really. It updates the progress once and that's it.
Also: using global static variables to communicate between a form and a background worker - ouch...
Also, you're using it wrong in my opinion. The work (CreateFilesInA ... CreateFilesInK) should be done by the background worker - that's what it is for. As the main thread will be blocked the way you implemented it, you will not see any updates otherwise.
The usual way to implement something like this is:
Create progress window and disable UI
Start background worker that does stuff in DoWork. In DoWork, after every call to a CreateFilesInXYZ method, call ReportProgress to the the UI be updated.
Update stuff in progress window whenever ProgressChanged event is fired
Hide progress window and enable your application's UI when background worker is done
The way you're doing it it's in no way asynchronous. So, actually, your code should look something like this:
public partial class MainWindow : Window
{
private BackgroundWorker backgroundWorker = new BackgroundWorker();
private MessageWithProgressBar progressWindow;
public MainWindow()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += ProgressChanged;
backgroundWorker.DoWork += DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
progressWindow = new MessageWithProgressBar();
progressWindow.Owner = this;
progressWindow.Show();
backgroundWorker.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
int numSteps = 11;
int currentStep = 0;
int progress = 0;
CreateFilesInA();
currentStep += 1;
progress = (int)((float)currentStep / (float)numSteps * 100.0);
worker.ReportProgress(progress);
CreateFilesInB();
currentStep += 1;
progress = (int)((float)currentStep / (float)numSteps * 100.0);
worker.ReportProgress(progress);
// All other steps here
...
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressWindow.progress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressWindow.Close();
WindowMsgGenDB msg = new WindowMsgGenDB();
msg.Show();
}
}
Please note that the above code goes into your main window! The MessageWithProgressWindow does not contain any code. Maybe the Window_Loaded event handler is not the right place to start the background worker, but you get the picture.

Stop running backgroundworker and start new one.

I have a window with a calendar and datagrid.
When the user selects a new date in the calendar I want to query the database for calls made on that date.
public HistoryDialog()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
HistoryGrid.SelectionChanged += new SelectionChangedEventHandler(HistoryGrid_SelectionChanged);
}
void calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
startDate = calendar.SelectedDates.OrderBy(x => x.Date).FirstOrDefault();
endDate = calendar.SelectedDates.OrderByDescending(x => x.Date).FirstOrDefault();
if (endDate != startDate)
SelectedDateTextBlock.Text = String.Format("{0:d MMMM}", startDate) + " - " + String.Format("{0:d MMMM}", endDate);
else
SelectedDateTextBlock.Text = String.Format("{0:d MMMM}", startDate);
SearchInDatabase();
}
private void SearchInDatabase()
{
if (worker.IsBusy)
worker.CancelAsync();
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
return;
}
var CallLog = ct.GetCalllogs(startDate, endDate, userID); // Database query
e.Result = CallLog;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IList CallLog = e.Result as IList;
foreach (CalllogInfo callInfo in CallLog)
{
chvm.CallHistoryList.Add(callInfo);
}
}
But when the user selects a new date while the backgroundworker is still running my program crashes.
How can I stop a running background worker and start a new one ?
Now I see you have set the WorkerSupportsCancellation property to true.
Now this doesnt actually cancel your BackgroundWorker it simply allows you to call the CancelAsync() method.
What you need to do is in your method processing periodically check to ensure the working is not pending cancellation from the CancellationPending property. As you check this property when you find it true you can set the Cancel property of the event arguments to true. This will then be available in your RunWorkerCompleted event. At this point (in your RunWorkerCompleted event handler) you can then restart the BackgroundWorker.
Here is an example using very basic background worker that supports cancellation and responds to the cancel request.
public MainWindow()
{
InitializeComponent();
this.DataContext = dataModel;
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += (o, e) =>
{
//do a long running task
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(500);
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
}
};
worker.RunWorkerCompleted += (o, e) =>
{
if (e != null && e.Cancelled)
{
startTheWorker();
return;
}
//TODO: I AM DONE!
};
}
BackgroundWorker worker;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (worker != null && worker.IsBusy)
worker.CancelAsync();
else if(worker != null && !worker.CancellationPending)
startTheWorker();
}
void startTheWorker()
{
if (worker == null)
throw new Exception("How come this is null?");
worker.RunWorkerAsync();
}
As you can see we are having to do all the work of actually cancelling the worker. Hope this helps.
In addition to the other answers, I want to add the following. The following block of code is responsible for raising the error:
private void SearchInDatabase()
{
if (worker.IsBusy)
worker.CancelAsync();
worker.RunWorkerAsync();
}
As you call CancelAsync, execution of your code is continued immediately without waiting until the BackgroundWorker is really stopped. This is the reason why RunWorkerAsync is called while the BackgroundWorker is still running resulting in the error you describe. You should change your code as follows:
private void SearchInDatabase()
{
if (worker.IsBusy)
worker.CancelAsync();
while(worker.IsBusy)
System.Threading.Thread.Sleep(100);
worker.RunWorkerAsync();
}
This assures that the BackgroundWorker finishes its work before starting a new one. In addition, you need to enhance your DoWork-method to check the CancellationPending property more often in order to really stop the BackgroundWorker soon after a cancellation request.
Your problem is coming from the cancellation.
When you are in ct.GetCalllogs your code is blocked and doesn't support cancellation.
In ct.GetCalllogs you should verify that the backgroundworker isn't in cancel state and then start again with the new value.
Here is an element of response how to stop backgroundworker correctly
Disallowing selections while the worker is doing work would remove the issue without the need to tackle it:
void calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
// .. cut ..
// disable the calendar and go on
calendar.IsEnabled = false;
SearchInDatabase();
}
private void SearchInDatabase()
{
// No longer needed: selection processing is now "atomic"
// if (worker.IsBusy) worker.CancelAsync();
worker.RunWorkerAsync();
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// .. cut ..
calendar.IsEnabled = true;
}
Or you could bind worker.IsBusy to calendar.IsEnabled to have it handled "automagically" (while still not having to worry about concurrent executions of the worker method).

Manipulating UI elements from within another thread

I'm trying to have a seperate thread in a WinForms C# application start a background worker which controls a ProgressBar (marquee). The issue is that when i try to set the bar to visible it just does nothing, and i've tried many forms of Invoke but they don't seem to help.
The following method progressBarCycle is called from a separate thread.
BackgroundWorker backgroundWorker = new BackgroundWorker();
public void progressBarCycle(int duration)
{
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.WorkerSupportsCancellation = true;
backgroundWorker.RunWorkerAsync(duration);
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(0);
DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument);
while (DateTime.Now <= end)
{
System.Threading.Thread.Sleep(1000);
}
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (!this.IsHandleCreated)
this.CreateHandle();
statusStrip1.Invoke((MethodInvoker)delegate
{
progressBar1.Visible = false;
});
// if (!this.IsHandleCreated)
// {
// this.CreateHandle();
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
// else progressBar1.Visible = false;
// }
// else
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
// else progressBar1.Visible = false;
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (!this.IsHandleCreated)
this.CreateHandle();
statusStrip1.Invoke((MethodInvoker)delegate
{
progressBar1.Visible = true;
});
// if (!this.IsHandleCreated)
// {
// this.CreateHandle();
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
// else progressBar1.Visible = true;
// }
// else
// if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
// else progressBar1.Visible = true;
}
Am I missing something obvious here? The comment sections are other things I've tried.
ProgressChanged is already raised on the UI thread (via the sync-context); your ProgressChanged does not need to do that Invoke - it can manipulate the UI directly (by contrast, DoWork can absolutely not do that). Perhaps the real problem is that you don't do any worker.ReportProgress(...) inside the loop - so it only happens once at the start.
Here's a full example:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var worker = new BackgroundWorker {
WorkerReportsProgress = true })
using (var progBar = new ProgressBar {
Visible = false, Step = 1, Maximum = 100,
Dock = DockStyle.Bottom })
using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" })
using (var form = new Form { Controls = { btn, progBar } })
{
worker.ProgressChanged += (s,a) => {
progBar.Visible = true;
progBar.Value = a.ProgressPercentage;
};
worker.RunWorkerCompleted += delegate
{
progBar.Visible = false;
};
worker.DoWork += delegate
{
for (int i = 0; i < 100; i++)
{
worker.ReportProgress(i);
Thread.Sleep(100);
}
};
btn.Click += delegate
{
worker.RunWorkerAsync();
};
Application.Run(form);
}
}
}
Run progressBarCycle from the UI thread. RunWorkerAsync will
create the new thread for you.
In backgroundWorker_ProgressChanged simply call
progressBar1.Visible = true;. There is no need for Invoke.
Better also add a progressBar1.Refresh(); .
Another possibility to be aware of is that the progress bar is running on your UI thread. In order for the progress bar to be displayed and redraw itself to show new progress amounts, the UI thread must be running freely, processing windows messages in the main application loop.
So if you start your background worker thread but then your UI thread sits in a busy wait loop waiting for it to complete, or goes off and does loads of other work, then it won't be processing windows messages and your progress bar will be "unresponsive". You need to release the UI thread so that this updating still happens (i.e. return from your event handler and allow the UI to continue running as normal).
The danger of this is that if the UI is active, then the user can still interact with it. You therefore have to write the UI to be aware when the background worker is active, and handle the situation properly (problems can include: Allowing the user to start the background worker again while it is already running, UI trying to display information while the worker thread is busily updating it, the user deciding to load a new document or quit while the background worker is busy, etc). The two main solutions to this are to wrap every bit of UI in a protective shield that stops anything dangerous being initiated while the background work is running (this can be a lot of work if you have lots of controls to wrap in this way, and it's easy to make a mistake that lets a bug slip through) or to leave the UI "unprotected" but add an IMessageFilter that stops all "dangerous" user interaction (clicks and keypresses) by suppressing their incoming windows messages (WM_KEYPRESS etc) while the background processing is active.

Run task asynchronously in C#

I have some process heavy tasks that run in my WinForms app. The problem is, while its running, it freeze the UI (UI main thread).
I haven't worked that much with threads and delegates in C# yet, and that's why I hope someone could help me to, how to handle those process heavy tasks, without freezing the UI, so the user don't think the app is crashing while waiting?
Eg. I have a call through my FrontController, that takes time:
_controller.testExportExcel(wrapper, saveDialog.FileName);
Since it's creating an Excel file. I won't the app to be responding on the UI while its working.
Another example of a process heavy task could be this:
private void dataGridView_liste_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
if (e.ListChangedType != ListChangedType.ItemDeleted)
{
foreach (DataGridViewRow r in dataGridView_liste.Rows)
{
DataGridViewCellStyle red = dataGridView_liste.DefaultCellStyle.Clone();
red.BackColor = Color.LightGreen;
if (r.Cells["News"].Value != null && (bool)r.Cells["News"].Value == true)
r.DefaultCellStyle = red;
}
}
}
Where the foreach loop takes time, and freeze the UI. An async thread running the process and automatically closing when its done, could be useful I think. But how does it work??
How about using a Task (if targetting .net 4)? This is considered as a replacement of the BackgroundWorker class since it supports nesting (parent/child tasks), task continuations, etc.
E.g.
private void dataGridView_liste_DataBindingComplete(object sender,
DataGridViewBindingCompleteEventArgs e)
{
Task t = Task.Factory.StartNew(() =>
{
// do your processing here - remember to call Invoke or BeginInvoke if
// calling a UI object.
});
t.ContinueWith((Success) =>
{
// callback when task is complete.
}, TaskContinuationOptions.NotOnFaulted);
t.ContinueWith((Fail) =>
{
//log the exception i.e.: Fail.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
}
I answered a very similar question here
It boils down to using BackgroundWorker.
msdn provides an example:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SL_BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker();
public Page()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
}
}
Everything that runs in the DoWork event handler is asynchronous.
Everything that runs in ProgessChanged/RunWorkCompleted's event handlers is on the UI thread.
For your first example, a call to _controller.testExportExcel(), a BackgroundWorker or Task Parallel Library call (i.e. Task.Factory.StartNew(...)) would be appropriate to satify your requirement of keeping the UI responsive. Plenty of examples floating around, including the other answers here.
For your second example, you will find you can't put this on a background thread since it appears to be code that manipulates the UI. Specifically, if the implementation of your BackgroundWorker's DoWork event handler, or the delegate you pass to Task.Factory.StartNew(), or the method for a plain old thread touch the UI, you are highly likely (/certain?) to get an exception stating "Cross-thread operation not valid".
The reason for this is covered in this question. But I'm more surprised actually this is slow enough that you want to make it asynchronous. There might be some simple ways to make this code more responsive - Control.SuspendLayout() and .ResumeLayout() springs to mind.

WinForm Multithreading. Use backgroundWorker or not?

I have a simple app which fires of a series of data intensive tasks. I'm not very experienced with WinForms and I was wondering the best way to do this without locking the interface. Can backgroundWorker be re-used, or is there another way to do this?
Thanks
BackgroundWorker is a thread that also includes notification synchronization. For example, if you wanted to update your UI when the scan completes, a regular Thread cannot access the UI objects (only the UI thread can do that); so, BackgroundWorker provides a Completed event handler that runs on the UI thread when the operation completes.
for more info see: Walkthrough: Multithreading with the BackgroundWorker Component (MSDN)
and a simple sample code:
var worker = new System.ComponentModel.BackgroundWorker();
worker.DoWork += (sender,e) => Thread.Sleep(60000);
worker.RunWorkerCompleted += (sender,e) => MessageBox.Show("Hello there!");
worker.RunWorkerAsync();
backgroundWorker can be used.
its benefit - it allows you to update a progress bar and interact with UI controls. (WorkerReportsProgress)
Also it has a cancellation mechanism. (WorkerSupportsCancellation)
You can use BackgroundWorker for such requirements. Below is a sample which updates a label status based on percentage task [long running] completion. Also, there is a sample business class which sets some value and the value is set back to UI via ProgressChanged handler. DoWork is the place where you write your long running task logic. Copy-Paste the code below after adding a label and backgroundworker component on a Winforms app & give it a shot. You may debug across various handler [RunWorkerCompleted, ProgressChanged, DoWork] and have a look at InitWorker method. Notice the cancellation feature too.
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
}
The background worker would be a good choice to start with
For more info look here
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Categories