samedi 14 mars 2015

Update GUI from background worker

Ok, I know that similar questions were already asked. But here is my thing:


Environment: Visual Studio 2010, C++, Win Forms, Dot Net Framework 4.0, Windows XP SP3.


Everybody says that you should not update the GUI directly from a background task. Well... I am doing it heavily without having the slightest problem. But why? Shouldn't I run into problems, crashes or any other oddities at least from time to time?


My application is a controlling app for projectors. Currently 40,000 lines of code and 7 backgroundworker threads. All backgroundworker threads were created in the Designer. Three of those threads need to do a lot of stuff. Their only output is the user interface, which has a huge set of labels, sliders, numeric input fields, picture boxes and list boxes. The threads use the same functions to update the GUI like the functions that are located directly in the main user interface thread. For the functions it is not clear from where they are called.


I took at first great care with several sub-functions to ensure that the GUI was only updated in the main thread and in the ProgressChanged function. But I am only human and so I lost a bit track of some things and so I accidentally wound up updating the GUI directly from the backgroundworker threads. I never saw any problems and I also got no reports from the users of the software that there are crashes or any other oddities that were not based on simple errors from my side.


So now I did the ultimate test: I added two backgroundworker threads that both rigorously fool around totally randomly with the GUI in an endless loop, both at the same time in order to test if something bad happens. The code makes the buttons flicker, list boxes are filled up and cleared, labels are filled with random text... and so on. Except for that it looks funny it all works perfectly without a single crash or any other strange thing.


So, as I said: It never crashed. It always works flawlessly. What am I missing? What is this "Don't update the GUI from another thread under any circumstances" all about? Is the way how I do it ok or is my computer a miracle PC?


Here is a part of my test code with the two threads:



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();
}
}

Aucun commentaire:

Enregistrer un commentaire