The Invoke pattern |
Written by Mike James | ||||||
Thursday, 16 September 2010 | ||||||
Page 4 of 5
Asynchronous callsThe BeginInvoke/EndInvoke methods of running a delegate on the thread that created the control are also available to run any delegate on a thread from the thread pool. All you have to do is wrap the method in a delegate and use the delegate class’s implementation of BeginInvoke/EndInvoke. For example, we can construct two delegates that do the same tasks as threads T1 and T2 defined earlier: delegate void asyncTask(); asyncTask T2 = delegate() We can use the Invoke method to run them synchronously, that is on the same thread that created them, the UI thread. T1.Invoke(); In this case T1 completes before T2 begins. To run them on separate threads, i.e. asynchronously, we change Invoke to BeginInvoke: IAsyncResult R1 = The EndInvoke methods simply wait for the delegates to finish. If you try this out you will discover it doesn’t work. All that happens is that the entire program hangs at the first EndInvoke – see if you can work out why before reading on. The reason is that when a thread calls addText2 this switches execution to the UI thread – but the UI thread is blocked waiting for the tasks to end and so doesn’t process the delegate! This is a good example of deadlock – two threads, the UI and either of T1 or T2, waiting for each other to do something both blocked by the other. The solution is simple – don’t block the UI thread. Unfortunately the simplest way of not blocking the UI thread is to use a callback method to clean up when the thread has finished and this is not always simple. For example, a suitable callback delegate that can clean up both of the threads is: AsyncCallback C=delegate(IAsyncResult ar) To make this work we need to call the asynchronous delegates, passing both the callback and the delegate itself as the ar parameter: IAsyncResult R1 = T1.BeginInvoke(C,T1); With this change it all works as advertised. The Invoke patternYou might be wondering why Invoke is used to run UI objects on the UI thread. Why not simply use locks to synchronise access to each control so that just one thread can be updating the control at a time. As long as each thread completes before the next one starts there is no danger of leaving the control in a strange, half-completed, state. The reason for adopting the Invoke approach is simply that the UI thread accesses controls as and when it needs to and it isn’t going to play by the same rules as any threads that you create and this means that you can’t limit access to controls to just one thread using locks - the UI thread will simply ignore them. For locks to work all of the threads trying to access the resource have to honour the locking mechanism. Any thread that doesn't play by the rules simply makes a mess of the whole enterprise. The Invoke pattern however can be implemented so as to force threads to behave nicely when they access an object. The object can test to see which thread is accessing it and throw an exception if it isn't the thread that created it - thus forcing the thread to access it via an Invoke. This Invoke pattern is useful enough for us to want to know how to use it in our own classes. Put simply, how to you give a class an Invoke method that allows a thread to run a delegate on a thread of the class’s choice? The full answer is quite complicated and involves implementing the ISynchronizeInvoke Interface which includes Invoke, BeginInvoke, EndInvoke and Invoke required. <ASIN:0471081124> <ASIN:0131857258> <ASIN:0321694694> <ASIN:0672330636> <ASIN:0735626707> <ASIN:0321658701> |
||||||
Last Updated ( Monday, 20 December 2010 ) |