我有一个运行多个线程的蒙特卡罗模拟,带有一个进度条,告知用户它是如何运行的。进度条管理使用Invoke在单独的线程中完成,但Form未更新。
这是我的代码:
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,每个monte-carlo线程都会转储它的结果。通过使用Count方法,我可以看到完成了多少次模拟并更新了用户。
答案 0 :(得分:3)
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
(确保正确调用其中的所有调用)然后使用{阻塞该线程并不重要{1}}。然后你也可以取消对.Join
的所有混乱调用。
此外,您在此处不需要拨打.DoEvents()
:
this.Refresh();
如果您没有阻止UI线程,控件将在没有它的情况下更新,并且您只需添加额外的工作。如果你 阻止了UI线程,那么添加 this.Msg.BeginInvoke((MethodInvoker) delegate() {
this.Msg.Text = sText;
this.Refresh();
});
调用就不会有帮助,因为UI线程不会自由执行它而不是免费的执行上一行。这是混乱的编程 - 随机添加代码,希望它能够工作,而不是检查和理解它为什么没有。
第2章:工作场所类比。
想象一下,UI线程就像经理一样。经理可以通过多种方式委派任务。正如你所做的那样使用.Refresh()
有点像经理给每个人一份工作 - 乔得到一份工作,露西得到另一份工作,比尔得到第三份,萨拉得到第四份。一旦每个人都完成了,经理就会有后续工作,所以他想出了一个尽快完成工作的计划。
在给每个人完成任务之后,经理立即坐在Joe的办公桌旁,盯着他,什么也不做,直到Joe完成。当乔完成时,他移动到露西的办公桌,检查她是否完成了。如果她不在那里等到露西完成,那就移到比尔的桌子上,盯着他,直到他完成......然后转移到萨拉的办公桌。
显然,这并不富有成效。此外,四位团队成员中的每一位都向他们的经理发送了电子邮件状态更新(.Join
),但他还没有读过他们中的任何一位,因为他一直把所有的时间都花在他们的办公桌上,盯着看在他们身边,等待他们完成任务。就此而言,他还没有做任何其他事情。老板们一直在问自己在做什么,他的手机响了,没有人更新每周的财务报告 - 没什么。经理没有能够做任何其他事情,因为他决定他需要坐在他的底部并观察他的团队工作直到他们完成。
经理没有回复......如果你等了,经理可能会再次回复。你想解雇经理吗?
[YES - FIRE HIM] [NO - 等待等待]
更好的是,人们会想,如果经理只是让每个人都去处理事情,然后继续做其他事情。他所关心的只是当他们完成工作时所需要的只是一个指示,让他们在工作完成时通知他。 UI线程就像您的应用程序管理员 - 它的时间很宝贵,您应该尽可能少地使用它。如果您有工作要做,请委托工作人员线程,不要让经理坐在那里等待其他人完成工作 - 让他们在事情准备就绪时通知并让经理重新开始工作。
答案 1 :(得分:0)
代码是非常偏僻的,但是如果
一目了然List<string> lines = new List<string>(File.ReadAllLines(@"C:\Users\RAG4ABT\Desktop\test.txt"));
int rowcount = metroGrid8.Rows.Count;
for (int i = 0; i < rowcount; i++)
{
int j = i + 1;
int lineIndex = lines.FindIndex(line => line.StartsWith("DASensor." + i + ".pos = "));
int lineIndex1 = lines.FindIndex(line => line.StartsWith("DASensor." + i + ".rot = "));
//if (("DASensor." +i+".name = S"+j) == ("DASensor." +i+".name = " + metroGrid8.Rows[i].Cells[0].Value.ToString()))
//{
if ((("DASensor." + i + ".name = S" + j) == ("DASensor." + i + ".name = " + metroGrid8.Rows[i].Cells[0].Value.ToString())) && (lineIndex != -1))
{
MessageBox.Show("Happy");
lines[lineIndex] = "DASensor." + i + ".pos = " + metroGrid8.Rows[i].Cells[1].Value.ToString() + " " + metroGrid8.Rows[i].Cells[2].Value.ToString() + " " + metroGrid8.Rows[i].Cells[3].Value.ToString();
File.WriteAllLines(@"C:\Users\RAG4ABT\Desktop\test.txt", lines);
}
}
以下代码未执行。这可能是问题吗?