Let's say I have the following function in C#:
void ProcessResults()
{
using (FormProgress f = new FormProgress()) {
f.ProgressAmount = 10;
// I want to have the following line run in a BackgroundWorkerThread
RetrieveAndDisplayResults();
f.ProgressAmount = 100;
}
}
What would I need to do for the line RetrieveAndDisplayResults(); to be run in a BackgroundWorkerThread?
var f = new FormProgress()
f.ProgressAmount = 10;
var worker = new BackgroundWorker();
worker.DoWork += (o, e) => RetrieveAndDisplayResults();
worker.RunWorkerCompleted += (o, e) =>
{
f.ProgressAmount = 100;
f.Close();
}
worker.RunWorkerAsync();
If your method is updating the UI, you'll have to change it to return the results, and then display them in the worker's RunWorkerCompleted event.
You can also use the ProgressChanged event and ReportProgress method to have more granular progress updates.
Your current approach is not well suited for using a Thread (Bgw or otherwise).
The main problem is the 'waiting' part before setting progress=100. What should the this method be doing in that time?
You can reverse the logic, launch a Bgw and use the Progress and Completed events to Update resp. Close your form.
You will probably have to change your approach, but the code below should be able to give a scaffolding for a long-running task that updates the UI as it progresses:
private void LaunchWorker()
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(OnDoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(OnProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnRunWorkerCompleted);
worker.RunWorkerAsync();
}
void OnDoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
while (aLongTime)
{
worker.ReportProgress(percentageDone, partialResults);
}
e.Result = results;
}
void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
var progress = e.ProgressPercentage;
var partialResults = e.UserState;
// Update UI based on progress
}
void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var results = e.Result;
// Do something with results
}
Related
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.
I am using multi threading in a C# application to upload images.
The flow is, first I am resizing the images.
second I am uploading them.
Code is like :
Upload button click
private void Button_Click_1(object sender, RoutedEventArgs e)
{
ResizeImagesAtLocal();
UploadImageToCloud();
}
private void ResizeImagesAtLocal()
{
var worker = new BackgroundWorker
{
WorkerReportsProgress = true
};
worker.DoWork += worker_ResizeImages;
worker.ProgressChanged += worker_ResizeProgressChanged;
worker.RunWorkerAsync();
}
private void worker_ResizeProgressChanged(object sender, ProgressChangedEventArgs e)
{
ResizeProgress.Value = e.ProgressPercentage;
}
private void worker_ResizeImages(object sender, DoWorkEventArgs e)
{
...
}
private void UploadImageToCloud()
{
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += worker_UploadImages;
worker.ProgressChanged += worker_UploadProgressChanged;
worker.RunWorkerAsync();
}
void worker_UploadProgressChanged(object sender, ProgressChangedEventArgs e)
{
UploadProgress.Value = e.ProgressPercentage;
}
void worker_UploadImages(object sender, DoWorkEventArgs e)
{
...
}
The problem is, ResizeImagesAtLocal is not yet finished, and the upload image started executing, which is causing issue..
Is there a way to make sure.. ResizeImagesAtLocal does all its job done, and then we can call the upload method.
You can use Task present from 4.0 version.
You can do (pseudocode):
Task.Factory.StartNew(()=> {
}). ContinuewWith(...);
ContinueWith will be called after StartNew task finished
Here's an approach to use if you're happy using Microsoft's Reactive Framework:
Func<Image, Image> resizeImage = i => { /* resize image */ };
Func<Image, System.Uri, bool> uploadImage = (i, u) => { /* upload image */ };
var uri = /* upload uri */;
var query =
from image in images.Select((x, n) => new { x, n }).ToObservable()
from thumbnail in Observable.Start(() => resizeImage(image.x))
from uploaded in Observable.Start(() => uploadImage(thumbnail, uri))
select new
{
image = image.x,
index = image.n,
thumbnail,
uploaded,
};
query
.ObserveOn(SynchronizationContext.Current)
.Subscribe(x =>
{
UploadProgress.Value = x.index * 100 / images.Count();
/* do other stuff if needed on UI thread */
});
That's it. It's all handled on background threads in parallel. It marshals the updates onto the UI thread. The Subscribe method returns an IDispose that can be used to stop the background threads. And it can all be coded in your Click event handler.
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).
public static void CalculateAttributions(BackgroundWorker worker, string _filename, ComboBox cmb, OpenFileDialog open)
{
worker = new BackgroundWorker { WorkerReportsProgress = true };
while (wave.Position != length)
{
...Process..
worker.ReportProgress((100 * (int)(length / wave.Position)) / (int)(length / mainBuffer.Length));
}
}
I wrote this method in a class to perform my calculations and using BackgroundWorker as parameter to show a Progressbarduring loop. However when i run this method in Form.cs
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = Math.Min(e.ProgressPercentage, 100);
}
this event is not working so ProgressBar value not changed. How can i make it work?
The report Progress event you have given here is for the backgroundworker1 and not the worker you coded within the function CalculateAttributions(...)
To enbale report progress and do progress bar changes.. Try the following code
Worker1.ProgressChanged+=new delegate {
progressBar1.Value = Math.Min(e.ProgressPercentage, 100);
};
with in the same function.
Hope this helps you
You will need to attach your event handlers. It should go like this.
public static void CalculateAttributions(BackgroundWorker worker, string _filename, ComboBox cmb, OpenFileDialog open)
{
worker = new BackgroundWorker { WorkerReportsProgress = true };
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
if (worker.IsBusy != true)
{
worker.RunWorkerAsync();
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (wave.Position != length)
{
...Process..
worker.ReportProgress((100 * (int)(length / wave.Position)) / (int)(length / mainBuffer.Length));
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = Math.Min(e.ProgressPercentage, 100);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//done
}
I think you should check this link.
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
The idea is to get your work inside background worker. and after each milestone report progress on your progress bar.
create background worker object
attach event handlers
call RunWorkerAsynch()
add code inside event handlers
do work will have the real work and report to progress call
report to progress will simply tell your progress bar to move one step ahead
completed will be called once everything is done. you may want to show a message to user that the operation is completed.
Currently I am working on a project which need to consume large amount data from web services,
There is service class sending input date to the server and get back the result,
due to it time consuming process, it is required to user combined progressbar and backgroundworker to show user the process percentage. I have browsing quite a few sample code on this topic, but still could not figure out the best way to do it. Would you please help,
My code is following,
private MyCollection[] callWebService(string[] Inputs, string method)
{
List<string> results = new List<string>();
string fiel dNames = ""; // todo - fix this if nothing left in loop
int sizeOfArray = 500;
for (int i = 0; i < Inputs.Length; i = i + sizeOfArray)
{
string[] outputRecords;
int errorCode;
string errorString;
string[] thisFiveHundred = createSubArray(Inputs, i, sizeOfArray);
iq.NameValuePair[] namevaluepairs = new iq.NameValuePair[0];
fieldNames = iqOfficeWebservice.BatchStan(method, thisFiveHundred, null, "", out outputRecords, out errorCode, out errorString);
results.AddRange(outputRecords);
}
results.ToArray();
IAddress[] formattedResults = convertStringsToInputs(fieldNames, results);
return formattedResults;
}
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() + "%");
}
Additional info can be found here.
Apart from the technical implementation in WPF or Winforms for example there is an essential aspect to consider.
Do you get feedback of the progress from the web services?
If not, you are likely not able to predict correctly the time the web service will need. This will depend on factors you cannot influence (like server load, network traffic, ...). In this case a progress bar is not recommended as it will give the user a weird experience.
Then you have options like displaying an text information that the request could take minutes and display a progress with an IsIndeterminate flag set (WPF) to show continuous progress feedback. A sandhour cursor is not an option as you are using a background thread.
An alternative is to break down the big request into smaller parts for which you can report progress.
I've just put together a variation of Adil's answer that encapsulates the work and updates, as well as properly detaching events and disposing of the worker. Please upvote Adil's answer if you upvote mine.
private void cmdButton_Click(object sender, EventArgs e)
{
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
DoWorkEventHandler doWork = (dws, dwe) =>
{
for (int i = 0; i < 101; i++)
{
worker.ReportProgress(i);
System.Threading.Thread.Sleep(100);
}
};
ProgressChangedEventHandler progressChanged = (pcs, pce) =>
{
lblProgress.Text = String.Format("Progress: {0}%", pce.ProgressPercentage);
};
RunWorkerCompletedEventHandler runWorkerCompleted = null;
runWorkerCompleted = (rwcs, rwce) =>
{
worker.DoWork -= doWork;
worker.ProgressChanged -= progressChanged;
worker.RunWorkerCompleted -= runWorkerCompleted;
worker.Dispose();
lblProgress.Text = "Done.";
};
worker.DoWork += doWork;
worker.ProgressChanged += progressChanged;
worker.RunWorkerCompleted += runWorkerCompleted;
worker.RunWorkerAsync();
}