Working with the User Interface from Another Thread
A Windows Form runs on a single thread, and any manipulation of the Form, including its properties and the properties of its controls, is supposed to occur only on that specific thread. This fact doesn't usually cause any problems, until you start creating background threads that, as in the file list example, want to update some aspect of the Form.
In the code provided so far, proper procedure has not been followed. Currently, the background process raises an event whenever a new file is found. Then the corresponding event handler on the Form adds the filename to the ListBox. This method appears to be correct because a procedure on the actual form (the event handler) does the only manipulation of the Form anywhere in the code. In reality, the nature of events and event handlers make this approach flawed. When a background process raises an event, any event handlers (regardless of where in your code they are located) are executed by the background process's thread.
You might be wondering, quite logically, why the code detailed so far appears to work just fine if it is incorrect. In some areas of programming (and threading is one of those areas), doing the wrong thing may cause a problem, but the code may also run fine. The end result of improperly manipulating a Form from another thread is an unstable system, and this instability may not show up in a predictable fashion. You must be careful to use threads correctly (including following this rule about Windows Forms), if you want to produce a solid system.
To properly ensure that the Form's thread is handling the ListBox update, you need to create a delegate pointing to a procedure that will do the actual UI update. Then you need to call that delegate using the Invoke method of your Form. Using this special Invoke method correctly executes the UI updating procedure using the Form's thread, as Listing 3 illustrates.
Listing 3: Use Invoke to Ensure That All UI Work Occurs on the Form's Thread
Dim WithEvents myScanner As FileScan Private Delegate Sub NewFileHandler(ByVal fileName As String) Private Sub frmWMAList_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Load lbFiles.Sorted = True myScanner = New FileScan() myScanner.Filter = "*.wma" Dim myThread As New _ System.Threading.Thread(AddressOf myScanner.StartScan) myThread.Start() End Sub Private Sub myScanner_FoundFile(ByVal fileName As String) _ Handles myScanner.FoundFile Dim myNewDelegate As NewFileHandler Dim myArgs(0) As Object myArgs(0) = fileName myNewDelegate = _ New NewFileHandler(AddressOf AddFileToList) Me.Invoke(myNewDelegate, myArgs) End Sub Private Sub AddFileToList(ByVal fileName As String) lbFiles.Items.Add(fileName) End Sub