views:

62

answers:

3

I've been staring at this thread for some time and I believe my mind has shut down on it. Thinking that the best thing to do, in order to update the time in a UI TextBox, would be to create what I thought would be a simple thread to get the time and post it back to the UI control. After having fought with it for a while, I'm getting frustrated and thinking that I might just add the time in some other way. In the intrepid spirit of the adventurer, I'm giving it another go.

I've got a similar thread operating elsewhere in the app that takes a list and populates a DataGridView in a TabControl. I'd have thought that the process would be roughly the same, but I'm missing a key part. The entirety of the thread is below:

private void displayTime()
    {
        while (true)
        {
            String time;
            String date;

            time = DateTime.Now.TimeOfDay.ToString();
            int len = time.IndexOf('.');
            time = time.Substring(0, len);
            date = DateTime.Now.Date.ToString();
            len = date.IndexOf(' ');
            date = date.Substring(0, len);

            updateClock(time, date);
        }
    }
private void updateClock(String time, String date)
    {
        if (InvokeRequired)
        {
            BeginInvoke(new timeDel(updateClock), new object[] {time, date});
            return;
        }           

        ctrlTimeTxt.Text = time + "\n" + date;
    }

The above thread has been started in various places(in an attempt to debug), but is currently in the Form's Shown event handler. The Form begins to appear, but then everything seems to hang. When I place a breakpoint in the thread, I can step ad infinitum, but the UI never seems to get control back. What am I missing? I'll be happy to expand upon any neglected details.

Edit: A clarification: This thread is being started in a function that is handling the Shown event. The description of the Shown event is given as: Occurs whenever the Form is first shown. I think that might eliminate the theory that the UI thread is Invoking too quickly.

+5  A: 

The problem is that your while loop here is generating so many events that the event queue gets flooded:

while (true)
{
    // ...
    updateClock(time, date);
}

Instead of doing this in a thread you can use a Timer to generate events at a regular interval and do one step of your method for each event. This will also mean that you don't have to use Invoke as the timer generates events on the main thread.

There are also two other errors in your code:

  • Occasionally TimeOfDay.ToString() could output a time that hits exactly at a second change, in which case the result of ToString() will not be "12:23:34.00000" but just "12:23:34" which will cause your method to throw an exception.
  • You are calling DateTime.Now twice, but the value could have changed in between the two calls. Normally this won't matter but as it passes midnight it could show '23:59:99.999' but the day shows as the next day. It's probably not a significant error in this application but you ought to get into the habit of only calling DateTime.Now once when you want consistent values for the date and time.

To fix these errors and to simplify your code:

  • Add a timer to your form.
  • Set the timer's Enabled property to true in the designer.
  • Add this event handler to the Tick event:

    private void timer_Tick(object sender, EventArgs e)
    {
        DateTime now = DateTime.Now;
        textBox1.Text = now.ToString("HH:mm:ss\nyyyy-MM-dd");
    }
    

You may wish to adjust the format string depending on your needs.

Mark Byers
That seems like it'd work as well. I don't know if it was you who initially posted to try Invoke() instead of BeginInvoke() (I believe it was...), but that ended up resolving my issue. Is there a reason NOT to use Invoke() and go with a timer instead?
Rich Hoffman
@Rich Hoffman: You are flooding the event queue. The reason why using Invoke instead of BeginInvoke works is because it causes your thread to wait until the form is updated before generating the next event, so the event queue doesn't grow out of control. However you are still unnecessarily spamming the event queue with a huge number of events which could cause problems with the responsiveness of your application if you also try to do something else at the same time.
Mark Byers
On the other hand the timer allows you to control the rate of events, plus you don't need the extra thread at all. It's a much simpler solution.
Mark Byers
@Mark Byers: I updated my post to resolve what I think was a misunderstanding. That being said, it seems like my [poor] attempt to thread this simple task might have introduced more trouble than was warranted. The Timer solution is likely to be the way I go.
Rich Hoffman
Spot on. This is a great example of misapproriated roles of the UI and worker threads. There has been so much fervor about using `BeginInvoke` or `Invoke` that people miss the obvious solution of reversing the roles. That is, instead of having the worker thread push data to the UI thread, it is often better to have the UI thread poll for it. There are so many advantages to doing this way.
Brian Gideon
+4  A: 

It doesn't surprise me that this isn't going well -- you're stuffing invokes in the UI thread message queue (with your BeginInvoke()) as fast as you can generate them. You may want to put a break (i.e. with System.Threading.Thread.Sleep()) in your displayTime() thread to give the UI thread time to do some work.

twon33
Yeah. I realize now that the BIG difference between Invoke and BeginInvoke is the asynchronous vs. synchronous nature. That was a big "oops" and a lesson learned. Thanks.
Rich Hoffman
You really don't want to do vanilla Invoke()s this fast either -- the problem is mostly the rate at which you're demanding that the UI thread do things and not so much whether you're requesting that it be done synchronously or asynchronously.
twon33
+2  A: 

How do you call displayTime?

If your Form.Show() method is like this

Show()
{
  displayTime();
}

Then as Mark answered you are blocking your main UI thread infinitely.

I would start a new thread

Show()
{
   Action displayTimeAction = displayTime;

   displayTimeAction.BegingInvoke(null, null);
}
David Basarab