好的,我知道已经提出了类似的问题。但这是我的事:
环境:Visual Studio 2010,C ++,Win Forms,Dot Net Framework 4.0,Windows XP SP3。
每个人都说你不应该直接从后台任务更新GUI。嗯......我做得很重,没有丝毫问题。但为什么?我不应该至少不时遇到问题,崩溃或任何其他奇怪的事情吗?
我的应用程序是投影仪的控制应用程序。目前有40,000行代码和7个后台工作者线程。所有后台工作线程都在Designer中创建。其中三个线程需要做很多事情。他们唯一的输出是用户界面,它有一大堆标签,滑块,数字输入字段,图片框和列表框。线程使用相同的函数来更新GUI,就像直接位于主用户界面线程中的函数一样。对于这些功能,不清楚它们的调用位置。
我首先非常注意几个子函数,以确保GUI仅在主线程和ProgressChanged函数中更新。但我只是人类,所以我失去了一些东西的跟踪,所以我不小心直接从后台工作线程更新GUI。我从来没有看到任何问题,我也没有收到软件用户的报告,说有崩溃或任何其他奇怪的事情并非基于我身边的简单错误。
所以现在我进行了最终的测试:我添加了两个后台工作线程,它们在无限循环中完全随机地与GUI一起愚弄,同时为了测试是否发生了不好的事情。代码使按钮闪烁,列表框被填充和清除,标签填充随机文本......等等。除此之外,它看起来很有趣,一切都能完美运行,没有一次崩溃或任何其他奇怪的事情。
所以,正如我所说:它永远不会崩溃。它始终完美无瑕。我错过了什么?这是什么“不要在任何情况下从另一个线程更新GUI”?我的工作方式是好的还是我的电脑是神奇的PC?
编辑:打破“不要更新”法律我应该期待什么?崩溃,因为垃圾收集可能会在内存中移动UI并且线程会产生内存问题?或者只是我不知道两个或三个(或7个)线程中的哪一个赢了并且正在覆盖来自另一个线程的数据?
这是我的测试代码的一部分,包含两个主题:
bool rndBool() {
return (rand()%10 >= 5 ? false : true);
}
// Test Thread 1
private:
System::Void backgroundWorkerTest1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
BackgroundWorker^ workerTest1 = dynamic_cast<BackgroundWorker^>(sender);
int i = 0;
while ( true ) { // Loop forever
if ( workerTest1->CancellationPending ) { e->Cancel = true; return; } // Cancel Thread
int sleeptime = 1;
doubleInput_CubeX->Value = (int)rndDouble(50, 1000);
doubleInput_CubeY->Value = (int)rndDouble(50, 1000);
Sleep(sleeptime); labelStatus->Text = String::Format("(Test1) {0}", i); i++;
Sleep(sleeptime); button_PowerOn->Enabled = rndBool();
Sleep(sleeptime); button_PowerStandby->Enabled = rndBool();
Sleep(sleeptime); buttonItem_PowerOn->Enabled = rndBool();
Sleep(sleeptime); buttonItem_PowerStandby->Enabled = rndBool();
Sleep(sleeptime); button_SaveAll->Enabled = rndBool();
Sleep(sleeptime); buttonItem_SaveAll->Enabled = rndBool();
Sleep(sleeptime); button_ReadCurrent->Enabled = rndBool();
Sleep(sleeptime); buttonItem_ReadCurrent->Enabled = rndBool();
Sleep(sleeptime); buttonX_MappingCroppingOff->Enabled = rndBool();
Sleep(sleeptime); buttonX_GeometryFactoryReset->Enabled = rndBool();
Sleep(sleeptime); button_LoadAdjustment->Enabled = rndBool();
Sleep(sleeptime); button_LoadFramework->Enabled = rndBool();
Sleep(sleeptime); buttonX_ColorFactoryReset->Enabled = rndBool();
Sleep(sleeptime); colorPickerButton_ServiceColor->Enabled = rndBool();
Sleep(sleeptime); buttonX_ServiceColorOff->Enabled = rndBool();
this->Refresh();
}
}
// Test Thread 2
private:
System::Void backgroundWorkerTest2_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) {
BackgroundWorker^ workerTest2 = dynamic_cast<BackgroundWorker^>(sender);
int i = 0;
while ( true ) { // Loop forever
if ( workerTest2->CancellationPending ) { e->Cancel = true; return; } // Cancel Thread
int sleeptime = 3;
doubleInput_CubeX->Value = (int)rndDouble(i, 50, 1000);
doubleInput_CubeY->Value = (int)rndDouble(i, 50, 1000);
Sleep(sleeptime); labelStatus->Text = String::Format("(Test2) {0}", i); i++;
Sleep(sleeptime); button_PowerOn->Enabled = rndBool();
Sleep(sleeptime); button_PowerStandby->Enabled = rndBool();
Sleep(sleeptime); buttonItem_PowerOn->Enabled = rndBool();
Sleep(sleeptime); buttonItem_PowerStandby->Enabled = rndBool();
Sleep(sleeptime); button_SaveAll->Enabled = rndBool();
Sleep(sleeptime); buttonItem_SaveAll->Enabled = rndBool();
Sleep(sleeptime); button_ReadCurrent->Enabled = rndBool();
Sleep(sleeptime); buttonItem_ReadCurrent->Enabled = rndBool();
Sleep(sleeptime); buttonX_MappingCroppingOff->Enabled = rndBool();
Sleep(sleeptime); buttonX_GeometryFactoryReset->Enabled = rndBool();
Sleep(sleeptime); button_LoadAdjustment->Enabled = rndBool();
Sleep(sleeptime); button_LoadFramework->Enabled = rndBool();
Sleep(sleeptime); buttonX_ColorFactoryReset->Enabled = rndBool();
Sleep(sleeptime); colorPickerButton_ServiceColor->Enabled = rndBool();
Sleep(sleeptime); buttonX_ServiceColorOff->Enabled = rndBool();
this->Refresh();
}
}