我正在尝试在项目中执行数据库密集型任务。这是一个演练:
我们需要搜索我们的工作人员数据库,我们称之为Locums,并为特定工作找到一个。当我们决定处理x个作业时,此过程开始。因此,只需单击一个按钮,我们就可以使用ProcessJobBatch()
方法进行处理。但是,此方法仅针对非常有限数量的Locums进行处理。因此,填充调度程序控件需要不到10秒。现在,一旦提供了有限数量的Locums,我们就需要运行后台任务来检查Locums的其余部分。大约有1250个!
因此,一旦ProcessJobBatch()
完成,BackgroundWorker
,BackgroundWorkerMoreLocums
就会消失。现在,这个工作人员基本上做了一个简单的循环:对于每个工作,请通过整个1250名员工。这需要太长时间。我需要使用替代策略来计划这个,我不能使用ATM,或者我需要为内部for-each循环显示辅助进度条。
更多说明:我们每天都会多次导入一批作业(10到70)。导入批处理后,应用程序会指示登录用户“首选”查找新创建的作业。用户已经有他最喜欢的位置列表(1到20)。他希望首先在他最喜欢的工作中分配工作。这是通过ProcessJobBatch()
完成的。但是,有两种情况会阻止那里的流量:
因此,我最终得到了一个与每个Locum匹配作业的方案。
问题: 第二个BackgroundWorker可以在BackgroundWorker的DoWork中运行吗? 我第二次扫描错了吗?
环境:Windows 7 Pro 64位,Visual Studio 2010,C#,.NET 4.0和Windows窗体
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
答案 0 :(得分:12)
BackgroundWorker
可以从另一个DoWork
的{{1}}处理程序中启动,但您需要了解使用此类方案的后果。当您从主UI线程启动后台工作程序时,BackgroundWorker
处理程序在线程池线程上执行,而DoWork
和ProgressChanged
在主UI线程上执行,使其安全您可以与Windows窗体控件进行交互。
当您从主UI线程启动worker时,可以保证这种情况,因为它会获取该线程上可用的RunWorkerCompleted
,并由windows窗体infra-structure初始化。
但是,当您从另一个worker的SynchronizationContext
处理程序启动后台工作程序时,您将从缺少导致DoWork
和{{1的同步上下文的线程池线程启动它处理程序也可以在线程池线程上执行而不是在主UI线程中执行,这使得您无法安全地从这些处理程序中与Windows窗体控件进行交互。
答案 1 :(得分:5)
让一个后台线程产生新的后台线程是很常见的。如果您在后台线程上扫描列表并处理另一个线程上的每个列表项,我认为这不是问题。
在这种情况下,没有后台工作人员在中。只有后台工作者开始其他线程。
你应该考虑的事情 -
如果您处理该事件,请注意您在已完成的事件处理程序中执行的操作。
考虑为小任务运行这么多线程的性能影响。您应该考虑使用PLINQ or parallel tasks,以便.Net可以处理输入的分区和结果的合并,从而为您提供最佳性能。