A look at DoEvents

Posted on November 19, 2007

6


Why did I write this article, and why should you read it

I have heard blanket statements lately that assert that DoEvents is evil but then I have also seen code that relies on DoEvents. In this article I will attempt to shed some light on why you might want to use DoEvents, possible problems with using it, and also suggest some alternatives to it. My goal with this article is to provide you enough information to help you make a decision to use DoEvents backed by an understanding of its pros and cons. This article is accompanied with code samples. Please feel free to post your feedback and questions in comments and I will try to respond to them.

So what is DoEvents?

DoEvents is a method available on the Application class in the System.Windows.Forms namespace. What is it good for? Well, Windows maintains a queue to hold various events like mouse click, resize, etc. While a control is responding to an event, all other events are held back in the queue. So if your application is taking unduly long to process a button-click, rest of the application would appear to freeze. Consequently it is possible that your application appears unresponsive while it is doing some heavy processing in response to an event. While you should ideally do heavy processing in an asynchronous manner to ensure that the UI doesn’t freeze, a quick and easy solution is to just call DoEvents() periodically to allow pending events to be sent to your application.

Here is an example to demonstrate the issue and how using DoEvents can solve the problem from a user’s perspective. You will find the executable and the code in the zip archive accompanying this article.

110707-2013-alookatdoev1.png

  1. Open the application and click on “Start Timer” button. The “Start Timer” button is wired to start a Windows.Forms.Timer.
            private void btnStartTimer_Click(object sender, EventArgs e)
            {
                this.timer1.Enabled = true;
                this.timer1.Start();
                this.timer1.Interval = 1000;
            }
  2. As you can see, the timer is configured to tick every 1000 ms (1 sec). The timer’s tick handler is written to append the current time to the textbox.
    private void timer1_Tick(object sender, EventArgs e)
    {
    this.txtLog.AppendText("Timer ticked at "
    + DateTime.Now.ToLongTimeString()
    + Environment.NewLine);
    }
  3. Now click on the “Start Processing” button and you will notice that the application freezes until the processing is finished. This button’s click event handler is written to simply loop over integers from 1 to 100000 and modify the Text property of the form to display each integer.
    private void btnStartProcessing_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < 100000; i++)
        {
            this.Text = "Processing " + i.ToString();
        }
    }
  4. Now click on the “Start Processing With DoEvents()” button and you will notice that the application is more responsive. This button’s click event handler is written to call DoEvents() in the loop which is similar to the “Start Processing” button.
    private void btnProcessWithDoEvents_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < 100000; i++)
        {
            this.Text = "Processing " + i.ToString();
            Application.DoEvents();
        }
    }

    The application became unresponsive when we didn’t call DoEvents() because none of the pending events were processed until all the handlers for btnStartProcessing’s click event returned (in our case there was just one handler). By calling DoEvents() inside btnStartProcessing_Click, the pending Tick events on the Timer were pushed to our code, text was appended to the Textbox, and the necessary controls were also repainted in a timely manner.Thus Windows.Forms.Application.DoEvents() gives us an easy way to improve the perceived performance of an application. However, as with everything else, there are some things we need to keep in mind when using this method.

Possible problems with DoEvents

  1. Wasted CPU cycles: Normally, btnStartProcessing will appear disabled until all the handlers associated with its click-event return. But if we call DoEvents from one of those event-handlers, the user would see that the button is enabled and think that nothing happened in response to him clicking on that button. He could potentially click on the button again. But this would cause your handler for the click-event to be called again. The new invocation of the handler would repeat the same work as the previous invocation of the handler. So what would you do to prevent this? Well, in this case you can just disable the button to prevent the users from clicking it again until the click event-handler is finished. However, this point is worth keeping in mind when using DoEvents. You could have this problem if you were, let’s say, doing some heavy lifting in response to a timer-tick and decided to call DoEvents() inside the handler for the tick event.
  2. Possibly garbled external resources: Okay imagine this. You have your processing logic inside a Windows.Forms.Timer. Again if your handler for Timer’s Tick event takes a long time, your application can appear unresponsive. So let’s say you had code like the following when you started:
    private void timer1_Tick(object sender, EventArgs e)
    {
        foreach (Item item in this.GetInputData())
        {
            this.SaveToDataFile(item);
        }
    }

    Basically what you intended to do was periodically get input-data by calling a method called GetInputData() and save each item to a file.You decided to use DoEvents to improve the perceived performance of your application.

    private void timer1_Tick(object sender, EventArgs e)
    {
        foreach (Item item in this.GetInputData())
        {
            this.SaveToDataFile(item);
            System.Windows.Forms.Application.DoEvents();
        }
    }

    With the DoEvents() call in place, the UI would certainly appear more responsive. But think about what can go wrong in this scenario.Well, here’s a hint. What if the timer’s interval is less than the time it takes to execute the handler for its tick event?
    Basically when you started you were guaranteed that the list of items returned by GetInputData would be saved one after the other to the data file by a call to SaveToDataFile(). Now that you are calling DoEvents(), it is possible that the timer1_Tick is re-entered causing the second set of results from GetInputData to be processed in the middle of the processing of results from the first call to GetInputData. You could end up with items from one data set interleaved with those in the second set which might be catastrophic in some scenarios and might even render the saved data unusable.The point here is that you need to consider if your handlers can be safely re-entered if you are using DoEvents.

  3. Stack-Overflows: You might even get a stack-overflow. With a little patience, we can reproduce the stack-overflow here, so stay with me here for a bit. Modify the handler for btnStartTimer’s click event as under:
    private void btnStartTimer_Click(object sender, EventArgs e)
    {
        this.timer1.Enabled = true;
        this.timer1.Start();
        this.timer1.Interval = 20;
    }

    Modify the handler for timer’s tick event as under:

    private void timer1_Tick(object sender, EventArgs e)
    {
        Thread.Sleep(50);
        Application.DoEvents();
    }

    Build and run the application. Click on the “Start Timer” button and wait for a couple of minutes. You will get a stack-overflow after sometime.

    110707-2013-alookatdoev2.png
    Here is what happened:

    1. Similar to the situation in the previous code listing, the handler for the timer’s tick event needs more time to run than the interval of the timer. The timer is set to tick every 20 milli-seconds, while the handler for its tick-event will need 50 milli-seconds at least.
    2. As a result, by the time the runtime reaches the Application.DoEvents() call, a timer tick will already be waiting.
    3. By calling Application.DoEvents() we cause another timer1_Tick event-handler to be called from inside the current timer1_Tick event-handler. So go to Step-1.
    4. Follow these steps long enough and you will have a stack-overflow in your mind and that’s what happened in your application too.

The code listings were deliberately constructed to demonstrate the issues. My advice is to keep the general ideas in mind and map them to the situation you are dealing with in practice.

So what should you do?

  • Continue using DoEvents: DoEvents isn’t evil. However if you’re going to use it, consider the problems mentioned above and make sure that you have safe-guards against them.
  • Utilize asynchronous processing:
    • Use a BackgroundWorker
    • Use Asynchronous delegates
    • Create a thread.
    • You will find samples utilizing these techniques in the zip archive accompanying this article. I highly recommend reading up on these topics before you decide to use them. I might write an article on these in the future depending upon the response to this one.

    Just keep in mind that you should only access properties of Windows Forms controls from the thread that created them. But what should you do if you need to access their properties from a separate thread? Fortunately, .NET provides a method called Invoke on the base Windows.Forms.Control class which all other controls derive from. Invoke takes a delegate as a parameter and calls the delegate on the thread that created the Windows Form controls. An article by James T. Johnson on CodeProject presents a nice overview of the technique.

Update on Feb 05, 2008:

Happened to stumble upon a nice blog post on the dangers of DoEvents. The article focuses on various scenarios in which you might be tempted to use DoEvents and suggests safer alternatives. Here’s a link:

http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx

Download

Click here to download a zipped archived containing the code referred to in this article.

About these ads