我正在使用BackgroundWorker
在后台执行一些繁重的操作,以便UI不会无响应。
但是今天我注意到,当我运行程序时,只使用了两个CPU中的一个。
有没有办法将所有 CPU与BackgroundWorker
一起使用?
这是我的简化代码,就好像你很好奇!
private System.ComponentModel.BackgroundWorker bwPatchApplier;
this.bwPatchApplier.WorkerReportsProgress = true;
this.bwPatchApplier.DoWork += new System.ComponentModel.DoWorkEventHandler(this.bwPatchApplier_DoWork);
this.bwPatchApplier.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.bwPatchApplier_ProgressChanged);
this.bwPatchApplier.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.bwPatchApplier_RunWorkerCompleted);
private void bwPatchApplier_DoWork(object sender, DoWorkEventArgs e)
{
string pc1WorkflowName;
string pc2WorkflowName;
if (!GetWorkflowSettings(out pc1WorkflowName, out pc2WorkflowName)) return;
int progressPercentage = 0;
var weWorkspaces = (List<WEWorkspace>) e.Argument;
foreach (WEWorkspace weWorkspace in weWorkspaces)
{
using (var spSite = new SPSite(weWorkspace.SiteId))
{
foreach (SPWeb web in spSite.AllWebs)
{
using (SPWeb spWeb = spSite.OpenWeb(web.ID))
{
PrintHeader(spWeb.ID, spWeb.Title, spWeb.Url, bwPatchApplier);
try
{
for (int index = 0; index < spWeb.Lists.Count; index++)
{
SPList spList = spWeb.Lists[index];
if (spList.Hidden) continue;
string listName = spList.Title;
if (listName.Equals("PC1") || listName.Equals("PC2"))
{
#region STEP 1
// STEP 1: Remove Workflow
#endregion
#region STEP 2
// STEP 2: Add Events: Adding & Updating
#endregion
}
if ((uint) spList.BaseTemplate == 10135 || (uint) spList.BaseTemplate == 10134)
{
#region STEP 3
// STEP 3: Configure Custom AssignedToEmail Property
#endregion
#region STEP 4
if (enableAssignToEmail)
{
// STEP 4: Install AssignedTo events to Work lists
}
#endregion
}
#region STEP 5
// STEP 5 Install Notification Events
#endregion
#region STEP 6
// STEP 6 Install Report List Events
#endregion
progressPercentage += TotalSteps;
UpdatePercentage(progressPercentage, bwPatchApplier);
}
}
catch (Exception exception)
{
progressPercentage += TotalSteps;
UpdatePercentage(progressPercentage, bwPatchApplier);
}
}
}
}
}
PrintMessage(string.Empty, bwPatchApplier);
PrintMessage("*** Process Completed", bwPatchApplier);
UpdateStatus("Process Completed", bwPatchApplier);
}
非常感谢您对此进行调查:)
答案 0 :(得分:5)
BackgroundWorker
在单个后台(ThreadPool)线程中完成工作。因此,如果计算量很大,它将大量使用一个CPU。 UI线程仍然在第二个运行,但可能(像大多数用户界面工作一样)花费几乎所有的空闲时间等待输入(这是一件好事)。
如果要将工作拆分为使用多个CPU,则需要使用其他一些技术。这可以是多个BackgroundWorker
组件,每个组件都可以完成一些工作,或者直接使用ThreadPool。通过TPL在.NET 4中简化了并行编程,这可能是一个非常好的选择。有关详细信息,您可以看到my series on the TPL或MSDN's page on the Task Parallel Library。
答案 1 :(得分:3)
每个 BackgroundWorker 仅使用一个线程来执行您告诉它要做的事情。要利用多个内核,您需要多个线程。这意味着要么是多个 BackgroundWorkers ,要么在 DoWork 方法中生成多个线程。
答案 2 :(得分:3)
backgroundworker本身只提供了一个额外的执行线程。它的目的是让它脱离UI线程,并且非常擅长这项工作。如果你想要更多线程,你需要自己提供。
这里很容易构建一个接受SPWeb参数的方法,并且只为每个对象反复调用Thread.Start();然后使用Thread.Join()或WaitAll()完成等待它们在BackgroundWorker结束时完成。但是,这将是一个坏主意,因为操作系统花费时间在所有线程之间执行上下文切换会导致效率降低。
相反,您希望强制系统仅在几个线程中运行,但至少两个(在本例中)。一个好的经验法则是(2n - 1),其中“n”是你拥有的处理器核心的数量......但是在各种情况下你想破坏这个规则。您可以通过使用ThreadPool,迭代SPWeb对象并将它们添加到您继续使用的队列或其他方式(如TPL)来实现此目的。
答案 3 :(得分:1)
BackgroundWorker
正在第二个CPU核心上运行一个新线程,让UI响应。
如果您使用的是.NET 4,请使用Task Parallel Library,这样可以提供更好的结果并使用两个内核。
答案 4 :(得分:0)
BackgroundWorker本身只是创建一个除主UI之外的单个线程来进行工作 - 它不是试图并行化该工作线程中的操作。如果您想跨多个工作线程分散您的工作,您应该使用TPL进行调查。请记住,并非所有任务都能很好地转换为并行执行,因此如果释放UI是您唯一的目标,那么这可能已经是您可以做到的最佳目标。
答案 5 :(得分:0)
这有潜在的陷阱,但你可能会因为使用Parallel.ForEach而获得一些利润:
而不是
foreach (SPWeb web in spSite.AllWebs)
{
//Your loop code here
}
你可以:
Parallel.Foreach(spSite.AllWebs, web =>
{
//Your loop code here
});
这基本上创建了一个Task(来自.NET 4.0中的Task API)来自TaskPool的每个项目和日程表,它将为您提供利用这些核心所需的一些并行性。
您必须解决可能由此产生的不可避免的并发问题,但这是一个很好的起点。您将至少修复一个事实,即您跨线程维护共享状态(进度计数器)。以下是对此的一些指导:http://msdn.microsoft.com/en-us/library/dd997392.aspx