Recently we came across some legacy WinForms application we needed to update with some new features. While expert testing the application, it was found that some old functionality is broken. Invalid cross-thread operation. Now before you take me for a newbie, I do have some experience with Windows Forms applications. I'm no expert, but I consider myself experienced with threading and know exactly how to manipulate GUI components from a worker thread.
Apparently the one who wrote this piece of software did not and just set UI control properties from a background thread. Of course it threw the usual exception, but it was encased in an all-catching try-catch block.
I spoke with a stakeholder to find out how long this functionality had been broken, and turned out it was fine before. I couldn't believe that, but he demoed me that the function actually works in PROD.
So our unrelated updates "broke" the software in three places, but I can't get my head around how it could have worked in the first place. According to source control, the bug has always been there.
Of course we updated the application with proper cross-thread UI handling logic, but now I'm in the unlucky position of having to explain to stakeholders with not much tech background why something that can't have worked like ever, did work fine until we did some unrelated changes to the system.
Any insights would be appreciated.
One more thing that came to mind: in two of the cases the background thread updated a ListView control that was on a non-selected (and thus non-displayed) TabPage control. The third time it was standard Label control (text property was set) that was initially empty and gets a Text assigned according to what the background thread finds.
Here's some code for you, very similar to the one we found:
private void form_Load(object sender, System.EventArgs e)
{
// unrelated stuff...
ThreadStart ts = new ThreadStart(this.doWork);
Thread oThread = new Thread(ts);
ts.Start();
// more unrelated stuff ...
}
public void doWork()
{
string error = string.Empty;
int result = 0;
try
{
result = this.service.WhatsTheStatus(out error); // lengthy operation
switch (result)
{
case 1:
this.lblStatus.Text = "OK";
break;
case -1:
this.lblStatus.Text = "Error";
this.lblError.Text = error;
break;
default:
this.lblStatus.Text = "Unknown";
break;
}
}
catch
{
}
}
Unfortunately I saw lblStatus being updated in the production environment and it isn't referenced from anywhere else in the entire application (except the Designer generated stuff, of course).