Learn VisualBasic.NET with Me: crash woes, threading template

It was looking pretty grim for the file copier. The main problems were twofold:

First, the app sometimes crashed on bad data. The Windows crash-reporting dialog box came up, and all work stopped until it was dismissed.

Second, after dismissing the dialog box, the app looped furiously, failing to process the remaining files, but marked them as completed (that’s a little design bug there). The problem was that the COM server was returning an exception, and it was being trapped and effectively ignored. I say “effectively” because a special call to kill the app, via WMI, didn’t clear the problem — the underlying COM server didn’t get stopped and unloaded.

The error could be cleared if I paused the batch job, and restarted it. That caused the thread that communicated with the COM server to abort, thus (probably) releasing the COM server. If only this could be automated.

[While editing the above paragraphs, I realized that if I can remove all references to the COM interface, the COM server might unload itself…. 11/19]

I didn’t think it was a simple fix. One problem was when the crash reporting dialog box came up, the thread waiting on the server stalled. There was no “time’s up” event that the app could have used to recover.

This implied that a supervisory thread would have to be implemented. The supervisory thread would not communicate with the COM server, but start a new thread to do that, and watch the thread to see how long it ran. If it ran too long, it would kill off the server (via the WMI API), and does some bookeeping to note that the file failed.

So, ultimately, three threads would run. The UI gets one thread. A supervisory thread is started by the UI. The supervisory thread starts the “real” worker thread.

To implement this, I decided to learn how to do threads properly in VB. The original app had a lot of ad-hoc code that wasn’t quite standard. It worked, and was okay, but there were simpler ways. The BackgroundWorker control was intended for this task. The sample code, however, wasn’t quite complete. So I came up with a generic template for a threaded app, based on the MSDN code. It’s slightly more complex than the MSDN, but may be useful as a template for an app.

This template controls a text box, a button, and a progress meter. It does things the thread-safe way.

Tomorrow, I’ll finish it off with a worker thread that will do something time-consuming.
<br />
Imports System<br />
Imports System.ComponentModel<br />
Imports System.Drawing<br />
Imports System.Threading<br />
Imports System.Windows.Forms

Public Class SupervisorForm
Public counter As Integer = 0

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Me.SupervisorThread.IsBusy Then
Me.SupervisorThread.CancelAsync()
Else
Me.SupervisorThread.RunWorkerAsync()
End If
End Sub
Private Sub SupervisorThread_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles SupervisorThread.DoWork
‘ Do not access the form’s BackgroundWorker reference directly.
‘ Instead, use the reference provided by the sender parameter.
Dim bw As BackgroundWorker = CType(sender, BackgroundWorker)

WorkLoop(bw)

‘ If the operation was canceled by the user,
‘ set the DoWorkEventArgs.Cancel property to true.
If bw.CancellationPending Then
e.Cancel = True
End If

End Sub

Private Sub WorkLoop(ByVal bw As BackgroundWorker)
While Not bw.CancellationPending
counter += 1
Me.SetText(counter)
bw.ReportProgress(Int(counter / 10))
System.Threading.Thread.Sleep(50)
End While
End Sub

Delegate Sub SetTextCallback(ByVal text As String)

Private Sub SetText(ByVal s As String)
If Me.TextBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, s)
Else
Me.TextBox1.Text = s
End If
End Sub

Private Sub SupervisorThread_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles SupervisorThread.ProgressChanged
ProgressBar(e.ProgressPercentage)
End Sub

Delegate Sub ProgressBarCallback(ByVal i As Integer)

Private Sub ProgressBar(ByVal i As Integer)
If Me.ProgressBar1.InvokeRequired Then
Dim d As New ProgressBarCallback(AddressOf ProgressBar)
Me.Invoke(d, i)
Else
Me.ProgressBar1.Value = i
End If
End Sub

End Class