Executing task onTextChanged freezes UI - c#

I have a textbox and a listbox in my app. Also i have a text file with many players. What I want is whenever the user enters some text, look in the players file and add the matching players to the matching list which is the data source for the listbox. The problem is that it seems to be very slow and UI freezes a short time but it's quite annoying.
This is the code i have:
private void tb_playername_TextChanged(object sender, EventArgs e)
{
//This method is used to show user the options he can choose with the text he has entered
List<string> matching_players = new List<string>();
foreach (var item in all_players)
{
string player = item.f_name + " " + item.l_name;
if ((player.IndexOf(tb_playername.Text, StringComparison.OrdinalIgnoreCase) >= 0))
{
matching_players.Add("(" + item.rating + ") " + item.f_name + " " + item.l_name);
}
}
if (tb_playername.Text.Length >= 4)
{
matching_players.Sort();
matching_players.Reverse()
listbox_matchingplayers.DataSource = matching_players;
}
}

The problem is that you are doing a relatively time consuming task in the event handler. Event handlers operate on the same thread which takes care of rendering your application and handle any other visual aspects of it, so if this thread is busy, it will not be in a position to react to user input immediately, hence freezing.
The standard approach to this problem is to offload the time consuming tasks to a Background Worker. The background worker will operate in a new thread thus allowing the main thread to continue handling UI events. This example should hopefully put you on the right track when it comes to using a background worker.
EDIT: As per your question, what you could do would be to start searching only when a particular amount of characters is entered, for instance 3, this would reduce the amount of time the background worker runs. If the user keeps on typing, you could stop the current background worker if running and launch a new one.
The background worker will fire an event when finished. You could use the RunWorkerCompletedEventArgs.Result to then extract the returned list act upon it.

private async void tb_playername_TextChanged(object sender, EventArgs e)
{
var text = (sender as TextBox).Text;
// Check length of the text
if (string.IsNullOrEmpty(text) || text.Length <= 3)
return;
// Check timer to not process if user still typing, by measuring the key stoke time
...
// Filtering
List<string> matching_players = await PlayerFilter(text);
// Minimize listbox layout time
listbox_matchingplayers.SuspendLayout();
listbox_matchingplayers.DataSource = matching_players;
listbox_matchingplayers.ResumeLayout();
}
//Time consuming method
private async Task<List<string>> PlayerFilter(string text)
{
//This method is used to show user the options he can choose with the text he has entered
return matching_players;
}
For details of the user typing check wait for user to finish typing in a Text Box

Related

Popup status window during a datagrid iteration update

I've spent 4 hours on this and totally failed.
I know that i need to use BackgroundWorker but all the tutorials refer to running a progress script on the actual form you are running the worker on.
I have a large datagrid, which the user can use a check box to "select all" and then press "UPDATE ALL"
This updates every grid with a bunch of options they choose.
For some users this may be 5 records which is nothing, but some might update 200 records with 5 options which takes about... 10-15 secs to iterate through them.
I have tried so many variations of running BGworker which loads a FrmLoading.Showdialog
Or trying to have BGworker "do work" running the code and then the main thread having the FrmLoading.Show()
However nothing is working.
If i have the update code in the background worker, it fails because the datagrid and everything is in a different thread.
The other way round, and it just hangs on FrmLoading.Show()
Any advice would be great.
I just can't seem to get my head around how to get this working for what seems to be an easy idea!
Current Update Code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//MessageBox.Show(Convert.ToBoolean(rowx.Cells["clnselected"].Value).ToString());
if (Convert.ToBoolean(rowx.Cells["clnselected"].Value) == true)
{
//if cycle has a value.
if (cmbcycle.SelectedIndex != -1)
{
rowx.Cells["clncycletype"].Value = cycle;
rowx.Cells["clnpackscollect"].Value = packs;
}
//if location has a value
if (cmblocation.SelectedIndex != -1)
{
location = Convert.ToInt32(cmblocation.SelectedValue);
rowx.Cells["clnlocation1"].Value = location;
}
if (cmbsize.SelectedIndex != -1)
{
size = Convert.ToInt32(cmbsize.SelectedValue);
rowx.Cells["clnpacksize"].Value = size;
}
if (chkDelivery.Checked == true)
{
rowx.Cells["clnDelivery"].Value = true;
}
if (chkSignSheet.Checked == true)
{
rowx.Cells["clnSigningSheet"].Value = true;
}
}
countupdated++;
}
foreach (DataGridViewRow row in dataGridpatients.Rows)
{
row.Cells["clnselected"].Value = false;
row.DefaultCellStyle.BackColor = Color.White;
}
cmbsize.SelectedIndex = -1;
cmblocation.SelectedIndex = -1;
cmbcycle.SelectedIndex = -1;
chkDelivery.Checked = false;
chkSignSheet.Checked = false;
#countupdated++;
I also have #CountSelected.
What i want to do is run this code above but have a popup overlay (dialog) with my logo + "Updating X%"
Where X = countupdated/countselected * 100
I now know i need to use the background worker and invoke for the above, but literally have no idea regarding how to invoke the grid and go from there.
I understand i need to invoke the variables I'm using
(eg. cmbcycle.SelectedIndex)
I know iterating through 150 records and updating individual cells is probably wrong,
My other option is creating a datatable from "selected" cells on that datatable
then Running the update via SQL instead of iterating through a bound table.
Then after the SQL i can re-create the table which will now have the new cell values updated in it?
Would that be a more appropriate way to do it?
Max rows on this table would be 200. Average ~70 so we are never talking 500 or 1000
EDIT:
So the checked answer works to run the background worker and refer to the controls on the form.
The issue is that if i do this:
backgroundWorker1.RunWorkerAsync();
splashy.ShowDialog();
Then the splash screen pops up after the background worker ends
If i do this:
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Then the popup semi-forms and hangs until the end of the background worker, at which time it closes
because of the RunWorkerCompleted event.
EDIT:
I have no updated the code in DoWork and used Invokes to refer to the controls.
This works and the code runs fine.
I now need a popup ot appear showing the progress through the updates.
splashy.InvokeBy(() =>
{
splashy.Show();
});
backgroundWorker1.RunWorkerAsync();
Does not work. It causes the popup but freeze
splashy.ShowDialog();
backgroundWorker1.RunWorkerAsync();
Allows the Dialog to show (not 'frozen' and distorted) However the Lab (lblprogress) does not update.
This is because the form never get to the RunWorker method, it is stuck at ShowDialog.
It would be a good idea to make modifications on your DataSource itself and then bind it with the DataGridView.
But as from your existing code if you want to access your controls/UI to update or change values from BackgroundWorker.RunWorkerAsync method or any other Thread call for that matter, you can create an extension method to .Invoke() the controls like:
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
Keep this static class under the same Namespace as your main class for convenience.
Thus this code:
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
Will become:
dataGridpatients.InvokeBy(() =>
{
foreach (DataGridViewRow rowx in dataGridpatients.Rows)
{
//your codes
}
});
Similarly,
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
Will become:
cmbcycle.InvokeBy(() =>
{
if (cmbcycle.SelectedIndex != -1)
{
//your codes
}
});
This way you van safely access your controls, while keeping your UI responsive at the same time. Update your Popup Status UI the same way!
This answer is based around o_O's answer.
The main issue is that i wanted the UI to actually update and the background worker to supply the splash.
Instead of running all the 'hard code' in the BGW, i left it in the original thread, but called a BGW to display a popup Dialog form.
so at the start of the "hard code" I used:
backgroundWorker1.RunWorkerAsync();
This called:
FrmSplash splashy;
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
splashy = new FrmSplash();
splashy.ShowDialog();
}
In order to remove the dialog box, at the end of the code in the GUI thread, i used:
splashy.InvokeBy(() =>
{
splashy.Close();
}
);
backgroundWorker1.CancelAsync();
Which uses the extension supplied by O_o
public static class MyExtensions
{
public static void InvokeBy(this Control ctl, MethodInvoker method)
{
if (ctl.InvokeRequired)
ctl.Invoke(method);
else method();
}
}
I have also built a label update into splashy
So i could call
splashy.InvokeBy(() =>
{
splashy.SetStatus(countupdated.ToString());
}
);
As i iterated through the datagridview rows. This updated the label on the splash screen :)

Appending text to a line in a listbox

Is there a way to append text to the last line in a ListBox? I'd like a listing to look like this:
processing file 1... OK
processing file 2... CRC error
processing file 3... OK
When I open the file for processing, I would write "processing file x" with ListBox.Add("processing file x"). When done processing, before moving on to the next file, I would like to append the processing results.
I could wait until processing is complete, and just write the whole line at once, but it can take 10-15 seconds to process a file and it makes the UI look unresponsive.
A solution would also let me append text like (% complete) or something to make the UI more active while processing. I prefer to do this with a ListBox because of its scrolling and line selection properties, if possible.
I've not been able to find any way to do this; any ideas would be welcome.
you can directly add items to the list box
listBox1.Items.Add("new item");
and you may need to refresh it
listBox1.Refresh();
Edit :
In case you want to update the last item, you need to remove the last item and then re add it
var lastItem= listBox1.Items[listBox1.Items.Count-1];
lastItem += results;
listBox1.Items.RemoveAt(listBox1.Items.Count-1);
listBox1.Add(lastItem);
Your problem is that a GUI event is processed on the same thread as the GUI rendering, so the UI will be unresponsive until it's done.
The quick and hacky solution is to just call Application.DoEvents() after each ListBox.Add call. The form will still be jittery if you try to drag it around, but that function will cause the GUI to update/render once.
The correct way to do it is to use a BackgroundWorker, which you can start from the GUI event and will process in the background on a separate thread. If you implement its ProgressChanged function, you can call this from DoWork:
(sender as BackgroundWorker).ReportProgress(i/(float)totalFiles,msgString)
And then in ProgressChanged, do your whole:
listBox.Add(e.UserState.ToString() + ", " + e.ProgressPercentage.ToString() + "% completed.")
Simple version:
var i = listBox.Items.Count - 1; // crash bug if there is no line to append to
listBox.Items[i] = listBox.Items[i] + message;
Or, for a more complete solution (works from a non UI thread):
public static class Util
{
public static async Task RunOnUiThread(Action a)
{
await Application.Current.Dispatcher.InvokeAsync(() => { a(); });
}
}
public partial class MainWindow : Window
{
private async Task Write(string message)
{
var action = new Action(() =>
{
var i = OutputListBox.Items.Count - 1;
if (i >= 0)
{
OutputListBox.Items[i] = OutputListBox.Items[i] + message;
}
else
{
OutputListBox.Items.Add(message);
}
});
await Util.RunOnUiThread(action);
}
}

BackgroundWorker strange issues

Turns out there was an issue with the GetUserByID method, then library was updated and the problem seems to have gone away, still learnt how to better access the GUI off thread.
I wrote an application using the TweetInvi library, It retrieves a users Followers and following, Also their Picture, a link to the picture and twitter ID.
It then iterates through the returned lists and displays them (all in different lists)
Now when I first started with this application I had everything run on the _Click event and ofcourse ir froze the UI until it had completed.
I have now moved the code over to a backgroundworker Thread and It's causing some quirky issues.
Sometimes it will 'choose' not to populate certain lists, other times it will.
Sometimes it will load all the lists right except for the Following you list, which filters which of your friends are following you back (with an If statement to filter out Verified accounts)
At first I read that trying to update the UI on the separate thread can cause strange errors, so I have removed any UI control changes except for the Lists it populates.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
//user name retrieved from text box, rest of this method will pull various bits of data back
var username = e.Argument.ToString();
var user = User.GetUserFromScreenName(username);
Properties.Settings.Default.LastHandle = boxUsername.Text;
Properties.Settings.Default.Save();
var usersTweets = user.GetUserTimeline(Convert.ToInt32(txtTweetAmount.Text)).ToList();
foreach (var userTweet in usersTweets)
{
lstSearchTweetList.Invoke((MethodInvoker)delegate
{
var searchList = lstSearchTweetList.Items.Add(userTweet.Text);
searchList.SubItems.Add(userTweet.CreatedAt.ToString());
});
}
var show = user.GetFollowers(500).ToList();
foreach (var friend in show)
{
string screenName = "#" + friend.ScreenName;
lstFriend.BeginInvoke((MethodInvoker)delegate
{
lstFriend.Items.Add(screenName); // runs on UI thread
});
}
var friends = user.GetFriends(500);
var followers = user.GetFollowers(500);
var result2 = followers.Where(follower => friends.All(friend => follower.Name != friend.Name));
int i2 = 0;
foreach (var res2 in result2)
{
string screenName = "#" + res2.ScreenName;
lstFollowingChecker.BeginInvoke((MethodInvoker)delegate
{
lstFollowingChecker.Items.Add(screenName);
});
i2++;
// lblFollowBackAmount.Text = Convert.ToString(i2);
}
var result = friends.Where(friend => followers.All(follower => friend.Name != follower.Name));
//lblFriendCount.Text = "(" + result.Count().ToString() + ")";
int i1 = 0;
foreach (var res in result)
{
if (res.Verified != true)
{
string screenName = "#" + res.ScreenName;
lstFollowerChecker.BeginInvoke((MethodInvoker)delegate
{
lstFollowerChecker.Items.Add(screenName);
});
i1++;
// lblCheckerCount.Text = Convert.ToString(i1);
}
}
backgroundWorker1.ReportProgress(1,username);
}
The function calling RunWorkerAsync()
private void btnFind_Click(object sender, EventArgs e)
{
//start backgroundworker and clear friends and search lists
pctProgressBar.Visible = true;
lstFriend.Items.Clear();
lstSearchTweetList.Items.Clear();
lstFollowerChecker.Items.Clear();
lstFollowingChecker.Items.Clear();
lstFriend.Items.Clear();
lstSearchTweetList.Items.Clear();
if (txtTweetAmount.Text == "")
{
txtTweetAmount.Text = "20";
}
backgroundWorker1.RunWorkerAsync();
}
My problem is the strange unexplainable errors are still occurring seemingly randomly.
If this is caused by the lists being updated in the background worker thread, what use is the background worker if I cant use it to do the intensive stuff
I will also include two pictures of a friends account as it better demonstrates the issue's so something handles etc will be blanked out.
First Problem is that it sometimes populates a list multiple times, and the "Not following you back list" should be returning #Theilluminati only once
Again it returns #Theilluminati but lists it twice.
There's also an issue of if I run the below code anywhere, the background worker does not run, that is, It will pull back the picture/name/location but the background worker doesn't run and If I try do it in the actual backgroundworker thread then the lists won't populate.
var username = boxUsername.Text;
var user = User.GetUserFromScreenName(username);
//string ImageURL = user.ProfileImageUrl;
//string biggerImageURL = ImageURL.Replace("_normal", "");
//txtImageURL.Text = biggerImageURL;
//pctDisplaypicture.ImageLocation = biggerImageURL;
//txtTwitterID.Text = user.Id.ToString();
//lblFriendCount.Text = "(" + user.FollowersCount + ")";
Any help at all would be appreciated, I'm now struggling to see the use of Backgroundworker if it can't unload work from the UI thread, Sorry for the long post, Thanks for reading.
Fix Attempts
I have disabled the find button while the task is running and the same issue still occurs.
I have also tried using if(working.Cancellationpending == true) to break out of loops once the task has completed once.
I have changed the list foreach loops to the below respectively, and passed the username as a variable instead of pulling it from the control, the problems seem to have just got worse, no lists at all populate now.
lstSearchTweetList.Invoke((MethodInvoker)delegate
{
lstSearchTweetList.Items.Add(userTweet.Text).SubItems.Add(userTweet.CreatedAt.ToString());
});
backgroundWorker1.RunWorkerAsync(boxUsername.Text);
var username = e.Argument.ToString();
I have tried both answers as solutions and both still lead to same issue's with differing severity, I am still stuck with the problem that uncommenting the code to retrieve name/picture etc still blocks the backgroundworker from running. No matter where it's run from.
You may need to use the Invoke method on the list controls that you are trying to update on the background thread like so:
string screenName = "#" + friend.ScreenName;
lstFriend.Invoke((MethodInvoker)delegate {
lstFriend.Items.Add(screenName); // runs on UI thread
});
One problem you can have with multi-threading is when you try to access shared resources (Collections, Files, etc.) from multiple threads deadlocking can occur as well as race conditions. In order to do this safely a locking object would be created in this case and lock the code that is accessing the shared resource. This way the resource can only be accessed one at a time.
//defined globally
object _MyLockingObject = new object();
and within a certain method locking a list:
lock(_MyLockingObject)
{
myList.Add(item);
}
You are breaking a fundamental rule in Windows GUI programming: never access a control from a thread that is not the same thread that created the control. Bad mojo things happen when you break this rule ;)
Pass the username value via backgroundWorker1.RunWorkerAsync(boxUsername.Text);, and read it via e.Arguments as string.
You then need to use BeginInvoke to interact with the UI controls. Ideally, you should optimize this lambda to suspend the control's layout, replace the entire list of items one call, and resume the control's layout.
// execute on the UI thread
this.BeginInvoke((Action)(() =>
{
lstFriend.Items.Add("#" + friend.ScreenName);
}), null);
I would use the async Control.BeginInvoke over the sync Control.Invoke option. There does not appear to be a reason to wait on the control to render your change.

Implementing a progress bar in WinForms app during parsing XML

I made an application that passes trough an XML file and "extracts" the entries that have a specific value of a certain attribute. The application works fine, but the XML file has over 52,000 entries and only 3 values to sort by (so naturally it takes some time to execute it). Because of that I want to implement a progress bar so I can follow the progress my application made. I googled it, but none of the solutions work for me (the application does it's work, but the progress bar doesn't move at all), so if you could help me that would be grate.
The code that extracts the entries is this:
XmlNodeList nodes;
string ExtractedXML = "";
private void extractingXMLToolStripMenuItem_Click(object sender, EventArgs e)
{
if (nodes.Count != 0)
{
foreach (XmlNode node in nodes)
{
if (String.Compare(node.ChildNodes[6].ChildNodes[0].Attributes.GetNamedItem("code").Value.ToString(), "CodeValue") == 0)
{
tempXML = node.InnerXml;
temp = "<" + node.Name + " code=\"" + node.Attributes.GetNamedItem("code").Value + "\">" + Environment.NewLine;
temp += tempXML + Environment.NewLine;
temp += "</" + node.Name + ">";
ExtractedXML += temp + Environment.NewLine;
temp = "";
}
}
textBox1.Text = ExtractedXML;
}
}
There is a ToolStripMenue item for each value with similar code, only the "CodeValue" changes. How can I implement the progress bar to show me how far did the application get?
Best thing to do, is to not process the XML on the UI thread.
The reason why you don't see the progressbar increasing, is because processing that XML probably uses all resources and the UI thread has no chance of updating it's controls, until processing the XML has finished.
You can use a BackgroundWorker to do the processing, and use the ProgressChanged event to let the UI know that it should update the progressbar.
By doing so, your application will remain responsive, and the UI will not freeze. The UI thread will be handed some CPU time as well, and it is able to update his controls and respond to events.
You should do your long running process inside of a separate thread, BackgroundWorker would be useful here. When kicking off your process call:
bgWorker.RunWorkerAsync(//pass in object to process)
Then have a ReportProgress event where you increment your progress bar e.g.
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
And finally, inside of the do work event you'll want to call report progress with your formula for getting the percentage completed, as well as passing in a processed result, here's an example i've used:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
bgWorker.ReportProgress((outputGrid.Rows.Count * 100) / importGrid.Rows.Count, result);
}

Running a BackgroundWorker within another BackgroundWorker

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.

Categories