Rubrik: Verschiedenes / Sonstiges | VB-Versionen: VB.NET | 07.02.06 |
Multithreading mit VB.NET 2005 Asynchrone Dateioperationen mit Multithreading verwirklichen | ||
Autor: Stefan Mähr | Bewertung: | Views: 38.174 |
www.visualsoft-net.de | System: WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 | Beispielprojekt auf CD |
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:
Imports System.IO Imports System.ComponentModel
Private Variablen:
' unser Thread Private th As BackgroundWorker ' wieviele Bytes wurden kopiert Private lngBytesCopied As Long ' Die Speicheradresse der Zieldatei Private lngSourceFileLength As Long
Die Ladeprozedur des Formulares:
Hier richten wir den Backgroundworkerthread ein und stellen den Status der UI her.
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load th = New BackgroundWorker AddHandler th.DoWork, AddressOf th_DoWork AddHandler th.RunWorkerCompleted, AddressOf th_Completed AddHandler th.ProgressChanged, AddressOf th_ProgressChanged th.WorkerReportsProgress = True th.WorkerSupportsCancellation = True Me.lblSource.Text = "<Source File>" Me.lblTarget.Text = "<Target File>" Me.cmdCopyAsync.Enabled = False Me.cmdCancel.Enabled = False Me.progress.Visible = False Me.lblProgress.Visible = False End Sub
Die Routinen für das Auswählen der Source und Zieldatei. Eigentlich selbsterklärend.
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
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
Hier wird der asnchrone Thread gestartet:
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
So jetzt das eigentliche Arbeitspferd, die th_DoWork Prozedur kopiert die Datei in einem eigenen Thread.
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
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.
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
Die Cancel-Operation:
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
Wenn der Thread abgeschlossen ist werden wir in der th_completed inklusive Status davon benachrichtigt.
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 MessageBox.Show("CopyThread finished" & vbCrLf & strFinished) Me.cmdCopyAsync.Enabled = True End Sub
Noch eine schnelle Sicherheitsüberprüfung, ob der Thread nicht rennt, wenn das Form geschlossen wird.
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
Man kann das Weiterlaufen der UI schön beobachten, indem man einfach während der Copy-Operation einen Text in die Textbox "txtTest" eingibt.