C#,多线程 - 表单未更新
本文关键字:更新 表单 多线程 | 更新日期: 2023-09-27 18:04:57
我有一个跨多个线程运行的蒙特卡洛模拟,带有进度条来通知用户它的进展情况。进度条管理是使用 Invoke 在单独的线程中完成的,但表单不会更新。
这是我的代码:
Thread reportingThread = new Thread(() => UpdateProgress(iSims, ref myBag));
reportingThread.Priority = ThreadPriority.AboveNormal;
reportingThread.Start();`
这是正在调用的函数:
private void UpdateProgress(int iSims, ref ConcurrentBag<simResult> myBag)
{
int iCount;
string sText;
if (myBag == null)
iCount = 0;
else
iCount = myBag.Count;
while (iCount < iSims)
{
if (this.Msg.InvokeRequired)
{
sText = iCount.ToString() + " simultions of " + iSims + " completed.";
this.Msg.BeginInvoke((MethodInvoker) delegate() { this.Msg.Text = sText; this.Refresh(); });
}
Thread.Sleep(1000);
iCount = myBag.Count;
}
}
我已经使用Application.DoEvents((和this.refresh((来尝试强制表单更新,但没有任何反应。
更新:这是调用上述函数的过程
private void ProcessLeases(Boolean bValuePremium)
{
int iSims, iNumMonths, iNumYears, iIndex, iNumCores, iSimRef;
int iNumSimsPerThread, iThread, iAssets, iPriorityLevel;
string sMsg;
DateTime dtStart, dtEnd;
TimeSpan span;
var threads = new List<Thread>();
ConcurrentBag<simResult> myBag = new ConcurrentBag<simResult>();
ConcurrentBag<summaryResult> summBag = new ConcurrentBag<summaryResult>();
this.Msg.Text = "Updating all settings";
Application.DoEvents();
ShowProgressPanel();
iSims = objSettings.getSimulations();
iNumCores = Environment.ProcessorCount;
this.Msg.Text = "Initialising model";
Application.DoEvents();
iNumSimsPerThread = Convert.ToInt16(Math.Round(Convert.ToDouble(iSims) / Convert.ToDouble(iNumCores), 0));
this.Msg.Text = "Spawning " + iNumCores.ToString() + " threads";
for (iThread = 0; iThread < iNumCores; iThread++)
{
int iStart, iEnd;
if (iThread == 0)
{
iStart = (iThread * iNumSimsPerThread) + 1;
iEnd = ((iThread + 1) * iNumSimsPerThread);
}
else
{
if (iThread < (iNumCores - 1))
{
iStart = (iThread * iNumSimsPerThread) + 1;
iEnd = ((iThread + 1) * iNumSimsPerThread);
}
else
{
iStart = (iThread * iNumSimsPerThread) + 1;
iEnd = iSims;
}
}
Thread thread = new Thread(() => ProcessParallelMonteCarloTasks(iStart, iEnd, iNumMonths, iSimRef, iSims, ref objDB, iIndex, ref objSettings, ref myBag, ref summBag));
switch (iPriorityLevel)
{
case 1: thread.Priority = ThreadPriority.Highest; break;
case 2: thread.Priority = ThreadPriority.AboveNormal; break;
default: thread.Priority = ThreadPriority.Normal; break;
}
thread.Start();
threads.Add(thread);
}
// Now start the thread to aggregate the MC results
Thread MCThread = new Thread(() => objPortfolio.MCAggregateThread(ref summBag, (iSims * iAssets), iNumMonths));
MCThread.Priority = ThreadPriority.AboveNormal;
MCThread.Start();
threads.Add(MCThread);
// Here we review the CollectionBag size to report progress to the user
Thread reportingThread = new Thread(() => UpdateProgress(iSims, ref myBag));
reportingThread.Priority = ThreadPriority.AboveNormal;
reportingThread.Start();
// Wait for all threads to complete
//this.Msg.Text = iNumCores.ToString() + " Threads running.";
foreach (var thread in threads)
thread.Join();
reportingThread.Abort();
this.Msg.Text = "Aggregating results";
Application.DoEvents();
this.Msg.Text = "Preparing Results";
Application.DoEvents();
ShowResults();
ShowResultsPanel();
}
如您所见,在我的调用调用之前,表单有许多更新,它们都工作正常 - 在每种情况下,我都使用 Application.DoEvents(( 进行更新。
myBag 是一个 ConcurrentBag,每个蒙特卡洛线程都会将其结果转储到其中。通过使用 Count 方法,我可以查看已完成的模拟次数并更新用户。
foreach (var thread in threads)
thread.Join();
这是你的问题。 您在此处阻止,因此在所有线程完成之前,UI 线程中不会更新任何内容。
这是一个关键点 - 每次附加到用户界面事件处理程序的代码块完成执行时,.DoEvents()
自然发生并自行发生。 作为开发人员,您的主要职责之一是确保在用户界面事件处理程序中执行的任何代码都能及时完成(最多几百毫秒(。 如果你以这种方式编写代码,那么永远不需要调用DoEvents()
......曾。
您应该始终以这种方式编写代码。
除了性能优势之外,使用线程的一个主要优点是它们本质上是异步的 - 要利用这一点,您必须相应地编写代码。 打破程序习惯是一项艰巨的任务。 您需要做的是完全忘记.Join
并在此处退出ProcessLeases
方法 - 让 UI 再次控制。
已经在处理线程中的更新,因此您所需要的只是完成通知,以便在所有线程完成其工作时选择新的处理程序。 您需要跟踪您的线程 - 让它们在完成时通知(即:在 UI 线程上调用一些委托等(,并且以任何处理它的方法执行类似操作
if (allThreadsAreFinished) // <-- You'll need to implement something here
{
reportingThread.Abort();
this.Msg.Text = "Preparing Results";
ShowResults();
ShowResultsPanel();
}
或者,您也可以简单地在后台线程中调用ProcessLeases
(确保正确调用其中的所有调用(,然后用.Join
阻止该线程就没关系了。 然后,您还可以取消所有混乱的呼叫.DoEvents()
。
此外,您不需要在此处调用this.Refresh();
:
this.Msg.BeginInvoke((MethodInvoker) delegate() {
this.Msg.Text = sText;
this.Refresh();
});
如果您没有阻止 UI 线程,则控件将在没有它的情况下正常更新,您只会白白添加额外的工作。 如果阻止 UI 线程,则添加.Refresh()
调用将无济于事,因为 UI 线程无法自由执行它,就像它无法自由执行上一行一样。 这是混乱的编程 - 随机添加代码希望它能工作,而不是检查和理解它不能工作的原因。
第2章:职场类比。
想象一下,UI 线程就像管理器。 经理可以通过多种方式委派任务。 像你所做的那样使用.Join
有点像经理给每个人一份工作要做——乔得到一份工作,露西得到另一份,比尔得到第三份,萨拉得到第四份。 一旦每个人都完成,经理就有后续工作要做,所以他想出一个计划,尽快完成它。
给每个人分配任务后,经理立即走到乔的办公桌前,盯着他,什么也不做,直到乔完成。 乔说完,就走到露西的办公桌前,看看她是否做完了。 如果她不是,他会在那里等到露西说完,然后走到比尔的办公桌前,盯着他看,直到他说完......然后移到萨拉的办公桌前。
显然,这是没有成效的。 此外,四名团队成员中的每一个都在向他们的经理发送电子邮件状态更新(Manager.BeginInvoke -> read your email!
(,但他没有阅读任何内容,因为他一直坐在办公桌前,盯着他们,等待他们完成任务。 就此而言,他也没有做任何其他事情。 老板们一直在问发生了什么事,他的电话一直在响,没有人更新每周的财务数据——什么都没有。 经理无能为力,因为他决定他需要坐在他的屁股上,看着他的团队工作,直到他们完成。
经理没有回应...如果您等待,经理可能会再次回复。 你想解雇经理吗?
[是 - 解雇他] [不 - 继续等待]
有人会想,如果经理只是让每个人都去做一些事情,然后继续做其他事情,那就更好了。 他所关心的只是他们什么时候完成工作,所以只需要再一个指令,让他们在工作完成时通知他。 UI 线程就像应用程序的管理器 - 它的时间很宝贵,您应该尽可能少地使用它。 如果您有工作要做,请委派给工作线程,不要让经理坐在那里等待其他人完成工作 - 让他们在事情准备就绪时通知他们,让经理返回工作。
好吧,代码非常部分,但一目了然
this.Msg.InvokeRequired == false
以下代码不会执行。这能成为问题吗?