Running a BackgroundWorker within another BackgroundWorker - c#

I'm attempting a very DB intensive task in a project. Here is a walk-through:
We need to search our DB of workers, we called Locums, and find one for a specific job. This procedure starts when we decide to process x number of jobs. So, on the click of a button, we process using the ProcessJobBatch() method. However, this method only process against a very limited number of Locums. So it takes less then 10 seconds to fill up a scheduler control. Now, once the limited number of Locums are served, we need to run a background task to check the rest of the Locums. There are around 1250 of them!
So, once ProcessJobBatch() finishes, a BackgroundWorker, BackgroundWorkerMoreLocums, goes off. Now, this worker basically does a simple loop: For each job, go through the whole 1250 employees. This takes way too long. I need to plan this out using an alternate strategy that I can't of ATM or I need to show a secondary progress bar for the inner for-each loop.
More Explanation: We import a batch of Jobs (10 to 70) numerous times on daily bases. Once a batch is imported, the application instructs the logged-in user to "Preference Find" those newly created jobs. The user already has a list of his favorite locums (1 to 20). He wants to distribute the jobs among his favorites first. That is done through ProcessJobBatch(). But, there are two scenarios that prevent the flow there and then:
What if certain jobs didn't fall to
any favorite locum?
What if there is a locum in the whole
DB who can do almost all the jobs but
since he isn't favorite?
So, I end up with a scenario of matching a job with each Locum.
Question:
Can second BackgroundWorker run within a BackgroundWorker's DoWork?
Am I doing the second scan wrong?
Environment: Windows 7 Pro 64-bit, Visual Studio 2010, C#, .NET 4.0, and Windows Forms
private void ButtonPreferenceFind_Click(object sender, EventArgs e) {
if (LookUpBatches.EditValue != null) {
JobBatch JobBatchSelected = DbContext.JobBatches.FirstOrDefault(job_batch=> job_batch.OID == LookUpBatches.EditValue.ToString());
if (JobBatchSelected != null && JobBatchSelected.Jobs.Count(condition => condition.JobStatusID == 1) > 0) {
if (XtraMessageBox.Show(String.Format("Are you sure to process {0} job(s)?", JobBatchSelected.Jobs.Count(condition => condition.JobStatusID == 1)), Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) {
ProcessJobBatch(JobBatchSelected);
IEnumerable<Job> SpecificJobs = from req_jobs in JobBatchSelected.Jobs
where req_jobs.JobStatusID == 1
select req_jobs;
ProgressBarControlPreferenceFinder.EditValue = 0;
ProgressBarControlPreferenceFinder.Properties.Minimum = 0;
ProgressBarControlPreferenceFinder.Properties.Maximum = SpecificJobs.Count() - 1;
BackgroundWorkerMoreLocums.RunWorkerAsync(SpecificJobs);
} else {
LookUpBatches.Focus();
}
} else {
XtraMessageBox.Show("Unable to retrieve the selected batch or the batch has no processable jobs.", Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
LookUpBatches.Focus();
}
} else {
XtraMessageBox.Show("Select a batch first.", Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
LookUpBatches.Focus();
}
}
#region Background Searching
private void BackgroundWorkerMoreLocums_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) {
try {
e.Result = GetTableData(e.Argument);
}
catch (Exception ex) {
XtraMessageBox.Show("Background Error: " + ex.Message, "Excite Engine 2", MessageBoxButtons.OK, MessageBoxIcon.Error);
e.Result = ex;
}
}
private void BackgroundWorkerMoreLocums_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e) {
// only display progress, do not assign it to grid
ProgressBarControlPreferenceFinder.Increment(e.ProgressPercentage);
}
private void BackgroundWorkerMoreLocums_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) {
if (e.Result is DataTable) {
//dataGridView1.DataSource = e.Result as DataTable;
}
else if (e.Result is Exception) {
}
}
private DataTable GetTableData(Object JobList) {
DataTable ResultDataTable = new DataTable();
ResultDataTable.Columns.Add();
IEnumerable<Job> JobBatchSelected = (IEnumerable<Job>)JobList;
IEnumerable<Locum> LeftOverLocums = from lefties in DbContext.Locums
//where SchedulerMatrixStorage.Resources.Items.Select(res => (long)res.Id).ToList().Contains(lefties.OID) == false
select lefties;
int NumOfJobsProcessed = 0;
List<KeyValuePair<long, TemporaryPreferenceFindLocum>> AlreadyPrefferedLocums = new List<KeyValuePair<long, TemporaryPreferenceFindLocum>>();
foreach (Job oneJob in JobBatchSelected) {
foreach (Locum oneLocum in LeftOverLocums) {
if (DbContext.Availabilities.Any(check => check.LocumID == oneLocum.OID && check.AvailableDate == oneJob.JobDate && check.AvailabilityStatusID == 1)) {
//This Locum can do this job
//Now check if he/she has been just alloted
if (AlreadyPrefferedLocums.Any(search => search.Key == oneLocum.OID && search.Value.JobDate == oneJob.JobDate) == false) {
//No? Cool!
//Add to the list to prevent double allocation
AlreadyPrefferedLocums.Add(new KeyValuePair<long, TemporaryPreferenceFindLocum>(oneJob.OID, new TemporaryPreferenceFindLocum(oneJob.JobDate, oneJob.OID, oneLocum.OID, oneLocum.FirstName + " " + oneLocum.LastName)));
}
else {
continue;
}
}
else {
//Not marked as Avaliable on the required job date...
continue;
}
}
NumOfJobsProcessed++;
BackgroundWorkerMoreLocums.ReportProgress((int)(NumOfJobsProcessed * 100F / (JobBatchSelected.Count() - 1)));
}
return ResultDataTable;
}
#endregion

A BackgroundWorker can be started from within the DoWork handler of another BackgroundWorker, but you need to be aware of the consequences of using such a scheme. When you start a background worker from your main UI thread the DoWork handler is executed on a thread pool thread while the ProgressChanged and RunWorkerCompleted are executed back on the main UI thread making it safe for you to interact with windows forms controls.
This scenario is guaranteed when you start the worker from the main UI thread because it picks up the SynchronizationContext available on that thread and which is initialized by the windows forms infra-structure.
However, when you start a background worker from the DoWork handler of another worker, you'll be starting it from a thread pool thread that lacks the synchronization context causing the ProgressChanged and RunWorkerCompleted handlers to also be executed on thread pool threads and not in your main UI thread making it unsafe for you to interact with windows forms controls from within those handlers.

It is quite common to have one background thread spawn new background threads. I don't think it is a problem if you scan the list on a background thread and process each list item on another thread.
In such cases there is no background worker within another. There is just a background worker starting other threads.
Things you should consider -
Be aware of what you do in the completed event handlers in case you handle that event.
Consider the performance implications of running so many threads for small tasks. You should consider using PLINQ or parallel tasks so that .Net can handle the partitioning of input and merging of results giving you optimum performance.

Related

Run .exe processes in parallel and update progress bar in .NET 4.5

I have a dataGridView where I run a Process for each entry
and then update a toolStripProgressBar based on the output from the Process.
I have looked at the following threads,
Run two async tasks in parallel and collect results in .NET 4.5
Progress bar in parallel loop invocation
how to update the progress bar from tasks running concurrently
but I am not sure how to change my current code to something along these lines.
The main difference from these threads, as I see it, is that my computations are done by an outside application,
which I then need to collect the output from.
I guess I have to define each Process as an async task and then somehow collect the output.
For simplicity the processes are equal weighted in the sample code.
private iNumProcesses;
private void RunApps()
{
iNumProcesses = dataGridView1.Rows.Count;
string sPath = .exe application path
for (int i = 0; i < iNumProcesses; i++)
{
string sArgs = dataGridView1.Rows[i]["Arguments"].ToString();
ExecuteProgram(sPath, sArgs);
}
}
private void ExecuteProgram(string sProcessName, string sArgs)
{
using (cmd = new Process())
{
cmd.StartInfo.FileName = sProcessName;
cmd.StartInfo.Arguments = sArgs;
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.ErrorDialog = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardError = true;
cmd.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
cmd.ErrorDataReceived += new DataReceivedEventHandler(SortOutputHandler);
cmd.Start();
cmd.BeginOutputReadLine();
while (!cmd.HasExited) { Application.DoEvents(); }
}
}
private void SortOutputHandler(object sender, DataReceivedEventArgs e)
{
Trace.WriteLine(e.Data);
this.BeginInvoke(new MethodInvoker(() =>
{
if (e.Data == "Start") { do something... }
else if (e.Data == "Finish") { do something... }
else if (e.Data == "End") { do something... }
else
{
// .exe application output numbers 1 through 100
toolStripProgressBar1.Value += Math.Round(Convert.ToInt32(e.Data)/iNumProcesses,0);
}
}));
}
How can I run the processes in parallel and update the progress bar
based on the output numbers 1 through 100 I get from the .exe applications?
Any advice or suggestions will be greatly appreciated.
My answer to this question is not particularly associated with "C#" and therefore I'm not going to speak directly in those terms: this unexpectedly-thorny issue is actually universal.
The first thing that you must do is to arrange to periodically update the user display. To avoid nasty race-conditions and other problems, you should have "the main thread" perform this task, driven by a millisecond timer. The thread consults a shared array of progress-information and updates all of the progress-bars accordingly. (If you want to use an event to avoid outright "timed waiting," feel free to do so.)
Since each of the launched child processes will have their own input and output streams, and will be writing to those streams asynchronously, you will find it necessary to spawn a "mommy thread" within your application to supervise each child. This thread corresponds to a particular external process, and, in fact, is the one that launches it. The thread continues to exist until it determines that the process that it launched has died.
The "mommy thread" observes the output-stream(s) of its appointed ward to determine its "progress." It updates the progress-variables in the shared array accordingly. (A key element of this design is that each "mommy thread" is able to pay 100% of its attention to just its child. And, because all the "mommies" are threads, they can easily share information with the thread that's updating those progress bars.)
Do you actually have to use semaphores-and-such to coordinate access between the mommy-threads and the main? Depending of course upon the exact internal implementation of that data-structure, the answer just might be, "probably not." (Oh, but it's probably safest to do it anyway.)

System.Threading.Thread.Sleep c#

good morning
am using C# to develop pc software
and am using this method in my software to delay loop between lines
System.Threading.Thread.Sleep(x);
but this make my software freeze .
how to do this method work with out freeze my application
my full code
private void button18_Click_3(object sender, EventArgs e)
{
for (int i = 0; i < lstgroups.Items.Count; i++)
{
//
System.Threading.Thread.Sleep(x);
System.Windows.Forms.Application.EnableVisualStyles();
//
timeless.Enabled = true;
timeless.Start();
duration--;
if (duration == 0)
{
timeless.Start();
}
if (Gsend.Post(lstgroupsbox.Items[i].ToString(), AppSettings.Default.AccessToken, txtStatus.Text, txtLink.Text) == true)
lblshow.Text = "Sent To : " + lstgroups.Items[i].Text;
lblsendid.Text = "Sent To : " + lstgroupsbox.Items[i];
{
//foreach (var item in lstgroups.Items)
//{
// if (Gsend.Post(item.ToString(), "", txtStatus.Text, "", 2, "", txtImagePath.Text) == true)
// MessageBox.Show("Post Has Been Sent");
//}
}
Avoid using Thread.sleep() as it will put the Main thread on hold for given time and your GUI will freeze.
Other Option
Start Timer with interval you needed (X). and it should be fine.
Use background worker thread, as sleep in background worker will not effect your main GUI.
Do the work on a thread that isn't the main thread. By using sleep in an event you are essentially halting any processing on the main thread.
Maybe you can use a Background Worker
Background Worker

Multiple background threads

I have one c# application that uses BackGroundWorker to do a group of tasks:
private void buttonStartCheckOut_Click(object sender, EventArgs e)
{
BackgroundWorker checkOuter = new BackgroundWorker();
checkOuter.DoWork += new DoWorkEventHandler(checkOuter_DoWork);
checkOuter.RunWorkerAsync();
checkOuter.RunWorkerCompleted += new RunWorkerCompletedEventHandler(checkOuter_RunWorkerCompleted);
}
void checkOuter_DoWork(object sender, DoWorkEventArgs e)
{
if (textBoxCICheckOut.Text != "")
CheckOutCI();
if (textBoxCACheckOut.Text != "")
CheckOutCA();
if (textBoxCAuthCheckOut.Text != "")
CheckOutCAuth();
if (textBoxCLCheckOut.Text != "")
CheckOutCL();
if (textBoxCCCheckOut.Text != "")
CheckOutCC();
}
As you can see, I have only 2 threads; one for GUI and one for secondary task.
Its easy for me to track when all the functions finish.
Now I want to make it more fast by creating a separate thread for CheckOutCI(), CheckOutCA() and others.Creating 5 background workers looks kinda dirty.
I want to ask:
How will I keep track of when all the functions have finished executing.
If any one function returned an exception, I want to display it to user and ask the user to correct the user and try again. I hope I am able to explain my question properly.
PLEASE edit the code by wdavo as per my comment on his post.
I'd look at using the Task library (Assuming you are running .NET 4.5 or later). I find it much better to use than background workers in most cases.
(Note you can still use the Task library in .NET 4, however Task.WhenAll is only available in 4.5)
http://msdn.microsoft.com/en-us/library/dd235618
Without rewriting your whole program, here's an example of how you would use it:
Move your simple conditional logic to the button
private void button1_Click(object sender, EventArgs e)
{
var tasks = new List<Task>();
if (Text == "A")
{
tasks.Add(funcA());
}
if (Text == "B")
{
tasks.Add(funcB());
}
//And so on....
Task.WhenAll(tasks.ToArray()).ContinueWith(t =>
{
if (t.Exception != null)
{
//One of the tasks threw an exception
MessageBox.Show("There was an exception!");
}
else
{
//None of the tasks threw an exception
MessageBox.Show("No Exceptions!");
}
});
}
We add the tasks to a collection so we can know when they all finish via Task.WhenAll. When all the tasks in the collection have finished, a message box will be displayed. If any of the tasks in the collection have thrown an exception, the Exception property of 't' will be populated. The specific exceptions exist as inner exceptions of this exception.
Move your threading code to individual task/functions. You'd create your checkout functions to look similar to this:
private Task funcA()
{
return Task.Factory.StartNew(() =>
{
try
{
//Code running here will be executed on another thread
//This is where you would put your time consuming work
//
//
}
catch(Exception ex)
{
//Handle any exception locally if needed
//If you do handle it locally, make sure you throw it again so we can see it in Task.WhenAll
throw ex;
}
//Do any required UI updates after the work
//We aren't on the UI thread, so you will need to use BeginInvoke
//'this' would be a reference to your form
this.BeginInvoke(new Action(() =>
{
//...
}));
});
}
What this does is the following
Creates a and runs a task which does some work on a thread from the thread pool
If there is an exception, we handle it locally .We re-throw the exception so that we can know that a task has failed when 'Task.WhenAll' is executed
Updates the UI after the work is done. You need to call BeginInvoke to run the code on the UI thread to avoid cross threading issues.
Starting up more threads than CPUs or cores can actually make your application slower. When there are more CPU-bound threads than CPUs the OS needs to context switch more often between the threads--which is hugely expensive and could result in the OS spending more time context switching between your threads than giving them time to work.
You can use the parallel aspect of the Parallel Task Library to automatically distribute your load across CPUs. For example:
Action[] actions = new Action[] {CheckOutCI, CheckOutCA, CheckOutCAuth, CheckOutCL, CheckOutCC};
Parallel.ForEach(actions, e=>e());
...which isn't exactly what you want; but should give you a general idea. e.g. populate actions based on current conditions.
You need to use ReportProgress method in backgroundworker
void checkOuter_DoWork(object sender, DoWorkEventArgs e)
{
if (textBoxCICheckOut.Text != "")
CheckOutCI();
checkOuter.ReportProgress(completionPercentage,"Error message");
The data sent in ReportProgress can be captured in checkOuter_ProgressChanged event
checkOuter_ProgressChanged(object sender,ProgressChangedEventArgs e)
{
int percentage = e.ProgressPercentage;
string message = e.UserState;
}

C# BackgroundWorker

I have a button that on click event I get some information from the network.
When I get information I parse it and add items to ListBox. All is fine, but when I do a fast double-click on button, it seems that two background workers are running and after finishing all work, items in the list are dublicated.
I want to do so that if you click button and the proccess of getting information is in work, this thread is stopping and only after first work is completed the second one is beginning.
Yes, I know about AutoResetEvent, but when I used it it helped me only one time and never more. I can't implement this situation and hope that you will help me!
Now I even try to make easier but no success :( : I added a flag field(RefreshDialogs)(default false), when the user clicks on button, if flag is true(it means that work is doing), nothing is doing, but when flag field is set to false, all is fine and we start a new proccess.
When Backgroundwork completes, I change field flag to false(it means that user can run a new proccess).
private void Message_Refresh_Click(object sender, EventArgs e)
{
if (!RefreshDialogs)
{
RefreshDialogs = true;
if (threadBackgroundDialogs.WorkerSupportsCancellation)
{
threadBackgroundDialogs.CancelAsync();
}
if (!threadBackgroundDialogs.IsBusy)
{
downloadedDialogs = 0;
threadBackgroundDialogs = new BackgroundWorker();
threadBackgroundDialogs.WorkerSupportsCancellation = true;
threadBackgroundDialogs.DoWork += LoadDialogs;
threadBackgroundDialogs.RunWorkerCompleted += ProcessCompleted;
threadBackgroundDialogs.RunWorkerAsync();
}
}
}
void ProcessCompleted(object sender, RunWorkerCompletedEventArgs e)
{
RefreshDialogs = false;
}
So you want to keep the second process running while the first works, but they shouldn't disturb each other? And after the first one finishes the second one continues?
Crude way: While loop:
if (!RefreshDialogs)
{
RefreshDialogs = true;
this becomes:
while(RefreshDialogs)
{
}
RefreshDialogs = true;
After you set it false the second process wwill jump out of the while. (Note this is extremly inefficent since both processes will be running all the time, i'm pretty sure the second one will block the first one, but with multitasking now it shouldn't, if it block use a Dispatcher.Thread)
Elegant way: Use A Semaphore
http://msdn.microsoft.com/de-de/library/system.threading.semaphore%28v=vs.80%29.aspx
If you find it impossible to have both processes running at the same time, or want another way:
Add an Array/List/int and when the second process notices there is the first process running, like with your bool, increase your Added variable, and at the end of the process, restart the new process and decrese the variable:
int number;
if (!RefreshDialogs)
{
RefreshDialogs = true;
your code;
if(number > 0)
{
number--;
restart process
}
}
else
{
number++;
}
I have to admit, i like my last proposal the most, since its highly efficent.
Make your thread blocking. That is easy;
lock(someSharedGlobalObject)
{
Do Work, Exit early if cancelled
}
This way other threads will wait until the first thread releases the lock. They will never execute simultaneously and silently wait until they can continue.
As for other options; why not disable the button when clicked and re-enable it when the backgroundworker completes. Only problem is this does not allow for cancelling the current thread. The user has to wait for it to finish. It does make any concurrency go away very easily.
How about this approach?
Create a request queue or counter which will be incremented on every button click. Every time that count is > 0. Start the background worker. When the information comes, decrement the count and check for 0. If its still > 0 restart the worker. In that your request handler becomes sequential.
In this approach you may face the problem of continuous reference of the count by two threads, for that you may use a lock unlock condition.
I hav followed this approach for my app and it works well, hope it does the same for you.
I'm not an Windows Phone expert, but as I see it has support for TPL, so following code would read nicely:
private object syncRoot =new object();
private Task latestTask;
public void EnqueueAction(System.Action action)
{
lock (syncRoot)
{
if (latestTask == null)
latestTask = Task.Factory.StartNew(action);
else
latestTask = latestTask.ContinueWith(tsk => action());
}
}
Use can use semaphores
class TheClass
{
static SemaphoreSlim _sem = new SemaphoreSlim (3);
static void Main()
{
for (int i = 1; i <= 5; i++)
new Thread (Enter).Start (i);
}
static void Enter (object name)
{
Console.WriteLine (name + " wants to enter");
_sem.Wait();
Console.WriteLine (name + " has entered!");
Thread.Sleep (1000 * (int) name );
Console.WriteLine (name + " is leaving");
_sem.Release(); }
}
}
I found the solution and thanks to #Giedrius. Flag RefreshingDialogs is set to true only when proccess is at the end, when I added items to Listbox. The reason why I'am using this flag is that state of process changes to complete when the asynchronous operation of getting content from network(HttpWebRequest, method BeginGetRequestStream) begins, but after network operaion is complete I need to make UI operations and not only them(parse content and add it to Listbox)My solution is:
private object syncRoot = new object();
private Task latestTask;
public void EnqueueAction(System.Action action)
{
lock (syncRoot)
{
if (latestTask == null)
{
downloadedDialogs = 0;
latestTask = Task.Factory.StartNew(action);
}
else if(latestTask.IsCompleted && !RefreshingDialogs)
{
RefreshingDialogs = true;
downloadedDialogs = 0;
latestTask = Task.Factory.StartNew(action);
}
}
}
private void Message_Refresh_Click(object sender, EventArgs e)
{
Action ac = new Action(LoadDialogs2);
EnqueueAction(ac);
}

How to update UI from another thread running in another class

I am currently writing my first program on C# and I am extremely new to the language (used to only work with C so far). I have done a lot of research, but all answers were too general and I simply couldn't get it t work.
So here my (very common) problem:
I have a WPF application which takes inputs from a few textboxes filled by the user and then uses that to do a lot of calculations with them. They should take around 2-3 minutes, so I would like to update a progress bar and a textblock telling me what the current status is.
Also I need to store the UI inputs from the user and give them to the thread, so I have a third class, which I use to create an object and would like to pass this object to the background thread.
Obviously I would run the calculations in another thread, so the UI doesn't freeze, but I don't know how to update the UI, since all the calculation methods are part of another class.
After a lot of reasearch I think the best method to go with would be using dispatchers and TPL and not a backgroundworker, but honestly I am not sure how they work and after around 20 hours of trial and error with other answers, I decided to ask a question myself.
Here a very simple structure of my program:
public partial class MainWindow : Window
{
public MainWindow()
{
Initialize Component();
}
private void startCalc(object sender, RoutedEventArgs e)
{
inputValues input = new inputValues();
calcClass calculations = new calcClass();
try
{
input.pota = Convert.ToDouble(aVar.Text);
input.potb = Convert.ToDouble(bVar.Text);
input.potc = Convert.ToDouble(cVar.Text);
input.potd = Convert.ToDouble(dVar.Text);
input.potf = Convert.ToDouble(fVar.Text);
input.potA = Convert.ToDouble(AVar.Text);
input.potB = Convert.ToDouble(BVar.Text);
input.initStart = Convert.ToDouble(initStart.Text);
input.initEnd = Convert.ToDouble(initEnd.Text);
input.inita = Convert.ToDouble(inita.Text);
input.initb = Convert.ToDouble(initb.Text);
input.initc = Convert.ToDouble(initb.Text);
}
catch
{
MessageBox.Show("Some input values are not of the expected Type.", "Wrong Input", MessageBoxButton.OK, MessageBoxImage.Error);
}
Thread calcthread = new Thread(new ParameterizedThreadStart(calculations.testMethod);
calcthread.Start(input);
}
public class inputValues
{
public double pota, potb, potc, potd, potf, potA, potB;
public double initStart, initEnd, inita, initb, initc;
}
public class calcClass
{
public void testmethod(inputValues input)
{
Thread.CurrentThread.Priority = ThreadPriority.Lowest;
int i;
//the input object will be used somehow, but that doesn't matter for my problem
for (i = 0; i < 1000; i++)
{
Thread.Sleep(10);
}
}
}
I would be very grateful if someone had a simple explanation how to update the UI from inside the testmethod. Since I am new to C# and object oriented programming, too complicated answers I will very likely not understand, I'll do my best though.
Also if someone has a better idea in general (maybe using backgroundworker or anything else) I am open to see it.
First you need to use Dispatcher.Invoke to change the UI from another thread and to do that from another class, you can use events.
Then you can register to that event(s) in the main class and Dispatch the changes to the UI and in the calculation class you throw the event when you want to notify the UI:
class MainWindow : Window
{
private void startCalc()
{
//your code
CalcClass calc = new CalcClass();
calc.ProgressUpdate += (s, e) => {
Dispatcher.Invoke((Action)delegate() { /* update UI */ });
};
Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod));
calcthread.Start(input);
}
}
class CalcClass
{
public event EventHandler ProgressUpdate;
public void testMethod(object input)
{
//part 1
if(ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs(status));
//part 2
}
}
UPDATE:
As it seems this is still an often visited question and answer I want to update this answer with how I would do it now (with .NET 4.5) - this is a little longer as I will show some different possibilities:
class MainWindow : Window
{
Task calcTask = null;
void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1
async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2
{
await CalcAsync(); // #2
}
void StartCalc()
{
var calc = PrepareCalc();
calcTask = Task.Run(() => calc.TestMethod(input)); // #3
}
Task CalcAsync()
{
var calc = PrepareCalc();
return Task.Run(() => calc.TestMethod(input)); // #4
}
CalcClass PrepareCalc()
{
//your code
var calc = new CalcClass();
calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate()
{
// update UI
});
return calc;
}
}
class CalcClass
{
public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5
public TestMethod(InputValues input)
{
//part 1
ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus
// alternative version to the extension for C# 6+:
ProgressUpdate?.Invoke(this, new EventArgs<YourStatus>(status));
//part 2
}
}
static class EventExtensions
{
public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent,
object sender, T args)
{
if (theEvent != null)
theEvent(sender, new EventArgs<T>(args));
}
}
#1) How to start the "synchronous" calculations and run them in the background
#2) How to start it "asynchronous" and "await it": Here the calculation is executed and completed before the method returns, but because of the async/await the UI is not blocked (BTW: such event handlers are the only valid usages of async void as the event handler must return void - use async Task in all other cases)
#3) Instead of a new Thread we now use a Task. To later be able to check its (successfull) completion we save it in the global calcTask member. In the background this also starts a new thread and runs the action there, but it is much easier to handle and has some other benefits.
#4) Here we also start the action, but this time we return the task, so the "async event handler" can "await it". We could also create async Task CalcAsync() and then await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false); (FYI: the ConfigureAwait(false) is to avoid deadlocks, you should read up on this if you use async/await as it would be to much to explain here) which would result in the same workflow, but as the Task.Run is the only "awaitable operation" and is the last one we can simply return the task and save one context switch, which saves some execution time.
#5) Here I now use a "strongly typed generic event" so we can pass and receive our "status object" easily
#6) Here I use the extension defined below, which (aside from ease of use) solve the possible race condition in the old example. There it could have happened that the event got null after the if-check, but before the call if the event handler was removed in another thread at just that moment. This can't happen here, as the extensions gets a "copy" of the event delegate and in the same situation the handler is still registered inside the Raise method.
I am going to throw you a curve ball here. If I have said it once I have said it a hundred times. Marshaling operations like Invoke or BeginInvoke are not always the best methods for updating the UI with worker thread progress.
In this case it usually works better to have the worker thread publish its progress information to a shared data structure that the UI thread then polls at regular intervals. This has several advantages.
It breaks the tight coupling between the UI and worker thread that Invoke imposes.
The UI thread gets to dictate when the UI controls get updated...the way it should be anyway when you really think about it.
There is no risk of overrunning the UI message queue as would be the case if BeginInvoke were used from the worker thread.
The worker thread does not have to wait for a response from the UI thread as would be the case with Invoke.
You get more throughput on both the UI and worker threads.
Invoke and BeginInvoke are expensive operations.
So in your calcClass create a data structure that will hold the progress information.
public class calcClass
{
private double percentComplete = 0;
public double PercentComplete
{
get
{
// Do a thread-safe read here.
return Interlocked.CompareExchange(ref percentComplete, 0, 0);
}
}
public testMethod(object input)
{
int count = 1000;
for (int i = 0; i < count; i++)
{
Thread.Sleep(10);
double newvalue = ((double)i + 1) / (double)count;
Interlocked.Exchange(ref percentComplete, newvalue);
}
}
}
Then in your MainWindow class use a DispatcherTimer to periodically poll the progress information. Configure the DispatcherTimer to raise the Tick event on whatever interval is most appropriate for your situation.
public partial class MainWindow : Window
{
public void YourDispatcherTimer_Tick(object sender, EventArgs args)
{
YourProgressBar.Value = calculation.PercentComplete;
}
}
You're right that you should use the Dispatcher to update controls on the UI thread, and also right that long-running processes should not run on the UI thread. Even if you run the long-running process asynchronously on the UI thread, it can still cause performance issues.
It should be noted that Dispatcher.CurrentDispatcher will return the dispatcher for the current thread, not necessarily the UI thread. I think you can use Application.Current.Dispatcher to get a reference to the UI thread's dispatcher if that's available to you, but if not you'll have to pass the UI dispatcher in to your background thread.
Typically I use the Task Parallel Library for threading operations instead of a BackgroundWorker. I just find it easier to use.
For example,
Task.Factory.StartNew(() =>
SomeObject.RunLongProcess(someDataObject));
where
void RunLongProcess(SomeViewModel someDataObject)
{
for (int i = 0; i <= 1000; i++)
{
Thread.Sleep(10);
// Update every 10 executions
if (i % 10 == 0)
{
// Send message to UI thread
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action)(() => someDataObject.ProgressValue = (i / 1000)));
}
}
}
Everything that interacts with the UI must be called in the UI thread (unless it is a frozen object). To do that, you can use the dispatcher.
var disp = /* Get the UI dispatcher, each WPF object has a dispatcher which you can query*/
disp.BeginInvoke(DispatcherPriority.Normal,
(Action)(() => /*Do your UI Stuff here*/));
I use BeginInvoke here, usually a backgroundworker doesn't need to wait that the UI updates. If you want to wait, you can use Invoke. But you should be careful not to call BeginInvoke to fast to often, this can get really nasty.
By the way, The BackgroundWorker class helps with this kind of taks. It allows Reporting changes, like a percentage and dispatches this automatically from the Background thread into the ui thread. For the most thread <> update ui tasks the BackgroundWorker is a great tool.
If this is a long calculation then I would go background worker. It has progress support. It also has support for cancel.
http://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx
Here I have a TextBox bound to contents.
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Write("backgroundWorker_RunWorkerCompleted");
if (e.Cancelled)
{
contents = "Cancelled get contents.";
NotifyPropertyChanged("Contents");
}
else if (e.Error != null)
{
contents = "An Error Occured in get contents";
NotifyPropertyChanged("Contents");
}
else
{
contents = (string)e.Result;
if (contentTabSelectd) NotifyPropertyChanged("Contents");
}
}
You are going to have to come back to your main thread (also called UI thread) in order to update the UI.
Any other thread trying to update your UI will just cause exceptions to be thrown all over the place.
So because you are in WPF, you can use the Dispatcher and more specifically a beginInvoke on this dispatcher. This will allow you to execute what needs done (typically Update the UI) in the UI thread.
You migh also want to "register" the UI in your business, by maintaining a reference to a control/form, so you can use its dispatcher.
Thank God, Microsoft got that figured out in WPF :)
Every Control, like a progress bar, button, form, etc. has a Dispatcher on it. You can give the Dispatcher an Action that needs to be performed, and it will automatically call it on the correct thread (an Action is like a function delegate).
You can find an example here.
Of course, you'll have to have the control accessible from other classes, e.g. by making it public and handing a reference to the Window to your other class, or maybe by passing a reference only to the progress bar.
Felt the need to add this better answer, as nothing except BackgroundWorker seemed to help me, and the answer dealing with that thus far was woefully incomplete. This is how you would update a XAML page called MainWindow that has an Image tag like this:
<Image Name="imgNtwkInd" Source="Images/network_on.jpg" Width="50" />
with a BackgroundWorker process to show if you are connected to the network or not:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
// Set up background worker to allow progress reporting and cancellation
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
// This is your main work process that records progress
bw.DoWork += new DoWorkEventHandler(SomeClass.DoWork);
// This will update your page based on that progress
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
// This starts your background worker and "DoWork()"
bw.RunWorkerAsync();
// When this page closes, this will run and cancel your background worker
this.Closing += new CancelEventHandler(Page_Unload);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BitmapImage bImg = new BitmapImage();
bool connected = false;
string response = e.ProgressPercentage.ToString(); // will either be 1 or 0 for true/false -- this is the result recorded in DoWork()
if (response == "1")
connected = true;
// Do something with the result we got
if (!connected)
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_off.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
else
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_on.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
}
private void Page_Unload(object sender, CancelEventArgs e)
{
bw.CancelAsync(); // stops the background worker when unloading the page
}
}
public class SomeClass
{
public static bool connected = false;
public void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int i = 0;
do
{
connected = CheckConn(); // do some task and get the result
if (bw.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
Thread.Sleep(1000);
// Record your result here
if (connected)
bw.ReportProgress(1);
else
bw.ReportProgress(0);
}
}
while (i == 0);
}
private static bool CheckConn()
{
bool conn = false;
Ping png = new Ping();
string host = "SomeComputerNameHere";
try
{
PingReply pngReply = png.Send(host);
if (pngReply.Status == IPStatus.Success)
conn = true;
}
catch (PingException ex)
{
// write exception to log
}
return conn;
}
}
For more information: https://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx

Categories