Overview:
The problem - unknown threads may call
Within TestExecWindow many interactions start with pushing a button at the GUI. Those actions are executed within the safe main GUI thread (dispatcher thread).
But we also react on asynhronous events which will arrive on some notification thread:
- When the test executable terminates we get informed by a system thread which is different from our GUI thread.
- Visual Studio sends notifications when something has changed (e.g. new startup project). Currently it seems that those notifications will already arrive on the safe GUI thread. But can we be sure for future versions?
Critical situations may occur when data are accessed both from GUI thread and some notification thread. Unsynchronized changes may lead to corrupt data and program crash. When concerning GUI elements (buttons, list boxes etc.) access is only allowed from GUI thread. Visual Studio will rise an exception when access is tried from some other thread.
A simple solution - synchronize via GUI thread
Assume we want to write a message to the log pane by calling the service function TestExecWindowControl.AddInfoToEventList. We cannot be sure which thread will call this function. But within GUI (TestExecWindowControl.xaml.cs) we can pass the call to the dispatcher thread:
public void AddInfoToEventList(string info)
{
lstEvents.Dispatcher.Invoke(new Action(() => AddInfoToEventListFromGuiThread(info)));
}
private void AddInfoToEventListFromGuiThread(string info)
{
lstEvents.Items.Add(info);
An analogous problem occurs when we receive the message about the end of the test process within main class TestExecWindow. In this case we also use the GUI thread for synchronization:
public void OnTestTerminated(bool in_success, string in_args, bool in_memLeaksDetected, TimeSpan in_executionTime)
{
Gui().TestTerminated(in_success, in_args, in_memLeaksDetected, in_executionTime);
}
The GUI again dispatches to the safe GUI thread:
public void TestTerminated(bool in_success, string in_args, bool in_memLeaksDetected, TimeSpan in_executionTime)
{
this.Dispatcher.Invoke(new Action(() =>
TestTerminatedWithinGuiThread(in_success, "", in_args, in_memLeaksDetected, in_executionTime)));
}
private void TestTerminatedWithinGuiThread(bool in_success, string infoAboutTest, string in_args, bool in_memLeaksDetected, TimeSpan in_executionTime)
{
m_executionTimer.Stop();
string info = in_args;
if (info == "")
{
info = "<no args set>";
}
string durationStr;
if (in_executionTime.TotalSeconds < 1.0)
{
durationStr = "<1s ";
}
else
{
double totalNumMinutes = in_executionTime.TotalSeconds / 60.0;
durationStr = totalNumMinutes.ToString("F1")+ "m ";
}
WriteLine(1, (!in_success ? "FAILED: " : (in_memLeaksDetected ? "FAILED (MEM_LEAKS): " : "OK: "))
+ durationStr + GetInfoAboutCurrentTest() + " " + info);
bool failure = !in_success || in_memLeaksDetected;
int idx = (int)m_curRunMode;
++m_numExecutedTests[idx];
if (failure)
{
++m_numFailedTests[idx];
}
bool testSucceededUpToNow = (m_numFailedTests[idx] == 0);
SetState(testSucceededUpToNow);