Background Worker Threads
IronPython & Windows Forms, Part IX
This is part of a series of tutorials on using IronPython with Windows Forms.
The BackgroundWorker Class
Recently we tried to add an 'activity indicator' (throbber) to a long running process in our Windows Forms application. Unfortunately we ran into difficulties.
The main problem was that we were using the wrong event to detect when a control lost the focus. You might think that LostFocus was the obvious choice . In fact this is a low level event only used when updating UICues. The correct event to use is Leave.
LostFocus is raised when the user clicks on the exit button, but Leave isn't. We spent part of today fixing all the places we used GotFocus and LostFocus and replacing them with Enter and Leave. Luckily it wasn't too many.
You provide the BackgroundWorker with your long running process as an event handler. It has a method to detect if one is already running, and raises an event when it has finished.
A common idiom in our code is to have our own event hooks. Rather than tightly coupling our objects together, they can raise events.
An approximation of the code structure we used is shown below. This is also a good  example of how to use the BackgroundWorker.
This code shows an event hook class , which provide the LongRunningStart and LongRunningEnd events which enable and disable the activity indicator: the throbber.
This is automatically triggered when the textbox Leave event is raised. (But I've omitted all the boiler-plate in setting up the form and textbox of course.)
from System.ComponentModel import BackgroundWorker
from System.Windows.Forms import Form, TextBox
self.__handlers = 
def __iadd__(self, handler):
def __isub__(self, handler):
def fire(self, *args, **keywargs):
for handler in self.__handlers:
self._worker = BackgroundWorker()
self.LongRunningStart = EventHook()
self.LongRunningEnd = EventHook()
self._worker.DoWork += lambda _, __: self.__longRunningProcess()
self._worker.RunWorkerCompleted += lambda _, __: self.LongRunningEnd.fire()
# This can be called directly if you need a
# synchronous call as well.
# The long running process will block the GUI from
# updating though.
# Just drop out if one is already running
if not self._worker.IsBusy:
# This starts __longRunningProcess on a background thread
# Do *lots* of stuff :-)
self.longRunning = LongRunning()
self.longRunning.LongRunningStart += self.enableThrobber
self.longRunning.LongRunningEnd += self.disableThrobber
self.textBox = TextBox
self.textBox.Leave += lambda _, __: self.longRunning.LongRunningProcessAsync()
# do something
# do something
To check if the BackgroundWorker is in the middle of running, we use the IsBusy Property.
To tell it what to do when started, we add our long running process to the DoWork Event, this is kicked off on a separate thread: so be careful !
This is actually launched by the RunWorkerAsync Method.
When our process (bad choice of word, hey) has finished, the RunWorkerCompleted Event is raised.
Notice that if we wrap our .NET event handlers in a lambda we don't need the sender and event arguments.
lambda _, __: self.LongRunningEnd.fire()
You can use the event argument sent to DoWork handlers to pass arguments when RunWorkerAsync is called, but this isn't shown.
Thanks to Davy Mitchell for pointing out a couple of errors in the code example which have now been fixed.
|||We certainly did...|
|||Well, at least in my opinion.|
|||Well, I hope so...|
|||Written from memory, not copied directly from Resolver. Let's hope my boss believes me. You register and unregister event handlers using the normal .NET idiom, minus the sender and event arguments.|
For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.
Last edited Fri Nov 27 18:32:35 2009.