Background
From the valuable advice I received here I have now moved all of my database intensive code to a backgroundworker, specifically the direct calls to the database. That code is executed during the backgroundworker’s DoWork event. If a DataTable is returned during the DoWork event, I set that DataTable to a class-wide variable. This is done, to avoid having to invoke the controls requiring the DataTable every time I run this code.
While that code is being executed, I have a label that is updated in the main UI thread, to let the user know that something is occurring. To update the label I use a timer, such that every 750 ms a “.” is appended to the label’s string.
The first thing that I noticed was that the backgroundworker’s RunWorkerCompleted event wasn’t triggering. To solve this I did an Application.DoEvents(); before each call I made to the backgroundworker. It was ugly, but it caused the event to trigger. If anyone has an alternative to fix this, I am all ears.
I then came across an interesting predicament. If I run the program within Visual Studio 2010, in the debugging mode, I get an InvalidOperationException error stating that the “Cross-thread operation not valid: Control ‘lblStatus’ accessed from a thread other than the thread it was created on.” This error occurs during the backgroundworker’s RunWorkerCompleted event, where I set the text of a label in the main UI thread. But, when I launch the application directly, through the executable, it works exactly as desired (i.e. the label’s text is set correctly).
Question
Can anyone explain what is going on / offer advice on how to improve upon this?
Code
I can’t post all of the code involved, but here’s some relevant stuff:
namespace Test
{
public partial class frmMain : Form
{
public static Boolean bStatus = false;
static Boolean bTimer = false;
System.Timers.Timer MyTimer = new System.Timers.Timer();
public frmMain()
{
InitializeComponent();
MyTimer.Elapsed += new System.Timers.ElapsedEventHandler(MyTimer_Elapsed);
MyTimer.Interval = 750; // Every 3/4 of a second
ExampleTrigger();
}
/// <Insert>Lots of unshown code here</Insert>
private void ExampleTrigger()
{
// This is used to simulate an event that would require the backgroundworker
Application.DoEvents();
bgw.RunWorkerAsync(0);
WaitText("Example - 1");
}
private static void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
bTimer = true;
}
// Update status text
private void WaitText(string txt)
{
MyTimer.Enabled = true;
lblStatus.Text = txt;
bStatus = false;
while (!bStatus)
{
if (bTimer)
{
txt = txt + ".";
lblStatus.Text = txt;
lblStatus.Update();
bTimer = false;
}
}
MyTimer.Enabled = false;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int iSelect = (int)e.Argument;
switch (iSelect)
{
case 0:
// Hit the database
break;
/// <Insert>Other cases here</Insert>
default:
// Do something magical!
break;
}
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bStatus = true;
lblStatus.Text = "Ready!"; // This is where the exception occurs!
}
}
}
Never run a
while()loop like that in the UI thread.You’re freezing the UI until the loop terminates; this defeats the purpose.
In addition,
System.Timers.Timerdoesn’t run callbacks in the UI thread.Use a WinForms Timer instead.
Once you switch to a WinForms timer, you can simply append to the label inside the timer callback, and disable the timer when the operation finishes.