Multithreading mit VB.NET 2005 Verfasst am: 29.06.2009, 15:15
Mit .NET brachte Microsoft den heiligen Gral des Mutlithreadings zu den VB Programmierern. Bis dahin mußten wir uns mit DoEvents vor UI Lockups bewahren, oder noch schlimmer Out Of Process ActiveX-Exen programmieren um diese zu verhinden. Programmieren mit Threads bringt jedoch neue Probleme mit sich wie Deadlocks, nicht mehr ansprechbare Controls da diese in einem anderen Thread laufen usw. Dieses kleine VB.NET 2005 Beispiel sollte einen kleinen übersichtlichen Einstieg in das Programmieren mit dem Backgroundworkerthread bieten. Das Beispiel sollte eine große Datei byteweise in einem eigenen Thread kopieren und dabei einen Progress darstellen. Die Operation sollte natürlich jederzeit abbrechbar sein. Dazu legen wir ein neues Windowsformsprojekt an und legen folgende Controls auf Form1:
2 x Label (lblSource, lblTarget)
4 x CommandButton (cmdSelectSource, cmdSelectTarget, cmdCancel, cmdCopyAsync)
1 x Statusstrip (Statusstrip1, darauf eine progressbar - progress und einen toolstripstatuslabel)
1 x Textbox (txtTest), mit der beweisen wir das die UI nicht einfriert
Nun zum Code: Wir importieren folgende Namespaces: Visual Basic: [code]Imports System.IO Imports System.ComponentModel[/code]
Die Routinen für das Auswählen der Source und Zieldatei. Eigentlich selbsterklärend.
Visual Basic: [code]Private Sub cmdSelectSource_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdSelectSource.Click
Dim openDlg As New OpenFileDialog With openDlg .Title = "Select file" .InitialDirectory = Application.StartupPath If .ShowDialog = Windows.Forms.DialogResult.OK Then Me.lblSource.Text = .FileName End If End With End Sub [/code]
Visual Basic: [code]Private Sub cmdSelectTarget_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdSelectTarget.Click
Dim fldBrowser As New FolderBrowserDialog With fldBrowser .ShowNewFolderButton = True If .ShowDialog = Windows.Forms.DialogResult.OK Then Me.lblTarget.Text = .SelectedPath Me.cmdCopyAsync.Enabled = True End If End With End Sub[/code]
Hier wird der asnchrone Thread gestartet: Visual Basic: [code]Private Sub cmdCopyAsync_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdCopyAsync.Click
If Me.lblTarget.Text.Length > 0 And Me.lblSource.Text.Length > 0 Then Me.cmdCopyAsync.Enabled = False Me.progress.Minimum = 0 Me.progress.Maximum = 100 Me.progress.Visible = True Me.lblProgress.Visible = True Me.cmdCancel.Enabled = True
' Start des Threads löst dann die th_DoWork Prozedur aus th.RunWorkerAsync() End If End Sub[/code]
So jetzt das eigentliche Arbeitspferd, die th_DoWork Prozedur kopiert die Datei in einem eigenen Thread.
Visual Basic: [code]Private Sub th_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Dim fi As New FileInfo(Me.lblSource.Text)
Dim inStream As FileStream = Nothing Dim outStream As FileStream = Nothing Dim buffer(2048) As Byte Dim offset As Integer = 0
Try inStream = New FileStream(fi.FullName, FileMode.Open) outStream = New FileStream(Me.lblTarget.Text & "" & fi.Name, FileMode.Create) lngSourceFileLength = inStream.Length Do If th.CancellationPending = True Then e.Cancel = True inStream.Close() outStream.Close() Exit Sub End If offset = inStream.Read(buffer, 0, buffer.Length) outStream.Write(buffer, 0, offset) lngBytesCopied += offset
' WICHTIG: Hier könen wir einen Progress darstellen th.ReportProgress(offset / inStream.Length * 100) Loop Until offset = 0
Catch ex As Exception MessageBox.Show(ex.Message)
Finally inStream.Close() outStream.Close() End Try End Sub[/code]
Jetzt stellen wir den Progress dar. Hierzu stellt uns die BackgroundWorker Klasse eine eigene Prozedur zur Verfügung um Threadsafe auf die Controls des Winforms zuzugreifen.
Visual Basic: [code]Private Sub th_ProgressChanged(ByVal sender As Object, _ ByVal e As ProgressChangedEventArgs)
Try Me.lblProgress.Text = lngBytesCopied.ToString & " Bytes From " & _ lngSourceFileLength.ToString & " Copied" Me.progress.Value = e.ProgressPercentage.ToString Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub[/code]
Die Cancel-Operation:
Visual Basic: [code]Private Sub cmdCancel_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdCancel.Click
If th.IsBusy Then th.CancelAsync() End If End Sub[/code]
Wenn der Thread abgeschlossen ist werden wir in der th_completed inklusive Status davon benachrichtigt.
Visual Basic: [code]Private Sub th_Completed(ByVal sender As Object, _ ByVal e As RunWorkerCompletedEventArgs)
Dim strFinished As String = String.Empty
If e.Cancelled Then strFinished = "Operation Cancelled" ElseIf e.Error IsNot Nothing Then strFinished = "Operation Error " & e.Error.Message Else strFinished = "Operation Sucessfull" End If
Noch eine schnelle Sicherheitsüberprüfung, ob der Thread nicht rennt, wenn das Form geschlossen wird.
Visual Basic: [code]Private Sub Form1_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If th.IsBusy Then th.CancelAsync() End If End Sub[/code]
Man kann das Weiterlaufen der UI schön beobachten, indem man einfach während der Copy-Operation einen Text in die Textbox "txtTest" eingibt.