Windows Forms and the Idle Loop

When you're writing Windows Forms applications it's always nice to be able to keep the UI responsive in order to avoid frustrating the user. I seem to remember reading a UI study ages back which said that if the UI hangs for about 1/10th of a second the user can detect it so you don't get a lot of time to play with.

There's a lot of asynchronous support in the .NET framework that helps you do work "on other threads". Some of that support is done intrinsically by classes such as the FileStream or HttpWebRequest and othertimes you get given something that's synchronous in its operation but you make it asynchronous by using a delegate and invoking the BeginXXX operation which will move the work to the managed ThreadPool.

Either way, at this point with Windows Forms it gets a bit trickier because the callback that you'll get is going to happen on another thread and UI controls have thread affinity which means you can't touch them from that other thread. This leads into the whole Control.Invoke pattern for making sure that what you do happens on the UI thread but it complicates programming quite a bit. The documentation for that is over here.

However, something that you don't see flagged very often is the possibility of completing your asynchronous work on the Application Idle loop rather than allowing another thread to wander into your Windows Forms code and then trying to deal with it by Control.Invoke

I wrote this little class to manage a list of work that needs to be completed and execute the callbacks on the Idle loop of the Windows Forms application. This means that callbacks would occur on your main thread rather than some other thread and hence, no need for locking or Control.Invoke. Here's the class (didn't spend a long time on this so it might not be exactly right so beware of that);

public class AsyncIdleQueue
{
    private AsyncIdleQueue()
    {
    }
    private class QueuedDetails
    {
        public QueuedDetails(IAsyncResult result, AsyncCallback callback)
        {
            _result = result;
            _callback = callback;
        }
        public bool InvokeIfComplete()
        {
            bool done = _result.IsCompleted;
   
            if (done)
            {
                _callback(_result);
            }
            return(done);
        }
        private IAsyncResult _result;
        private AsyncCallback _callback;
    }
    static AsyncIdleQueue()
    {
        Application.Idle += new EventHandler(OnApplicationIdle);
    }
    public static void AddEntry(IAsyncResult result, AsyncCallback callback)
    {
        Entries.Add(new QueuedDetails(result, callback));
    }
    private static ArrayList Entries
    {
        get
        {
            if (_entries == null)
            {
                _entries = new ArrayList();
            }
            return(_entries);
        }
    }
    private static void OnApplicationIdle(object sender, EventArgs e)
    {
        for (int i = 0; i < Entries.Count; ++i)
        {
            QueuedDetails d = (QueuedDetails)Entries[i];
   
            if (d.InvokeIfComplete())
            {
                Entries.Remove(d);
                --i;
            }
        }
    }
    private static ArrayList _entries;
}

I can make use of this with code such as;

FileStream fs = new FileStream(d.FileName, FileMode.Open, FileAccess.Read);
   
byte[] array = new byte[fs.Length];
   
IAsyncResult r = fs.BeginRead(array, 0, array.Length, null,
    new object[] { fs, array} );
   
AsyncIdleQueue.AddEntry(r, new AsyncCallback(OnReadComplete));

Note that here we don't supply a callback directly to the BeginRead operation, we instead supply one when we call AddEntry on the AsyncIdleQueue and when we get called back we can do something like;

private void OnReadComplete(IAsyncResult result)
{
  object[] state = (object[])result.AsyncState;

  FileStream fs = (FileStream)state[0];
  byte[] array = (byte[])state[1];

  fs.EndRead(result);

}

to complete our asynchronous operation. In doing that we haven't directly been involved with any threading issues whatsoever and there's no need to call Control.Invoke or anything like that.

You might also like...

Comments

Mike Taulty

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“God could create the world in six days because he didn't have to make it compatible with the previous version.”