Die Community zu .NET und Classic VB.
Menü

VB.NET-Tipp 0003: Asynchrones Streaming

 von 

Beschreibung

Zeitaufwändige Stream-Operationen können zum Einfrieren der Benutzeroberfläche führen. Daher hat Microsoft die Stream-Klasse mit asynchronen Lese- und Schreibfunktionen ausgestattet. Die Verwendung dieser Funktionen ist allerdings wesentlich umständlicher als selbst einen Nebenthread zu erstellen (explizites Threading) oder einen Delegaten asynchron aufzurufen und dann (im Nebenthread) synchron zu operieren.

Dieser Tipp stellt die oben genannten Varianten des Threadings einander gegenüber: Stream.BeginRead, Delegate.BeginInvoke, explizites Threading und Backgroundworker.

Schwierigkeitsgrad:

Schwierigkeitsgrad 2

Framework-Version(en):

.NET Framework 2.0, .NET Framework 3.0, .NET Framework 3.5

.NET-Version(en):

Visual Basic 2005, Visual Basic 2008

Download:

Download des Beispielprojektes [18,52 KB]

' Dieser Quellcode stammt von http://www.activevb.de
' und kann frei verwendet werden. Für eventuelle Schäden
' wird nicht gehaftet.

' Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum.
' Ansonsten viel Spaß und Erfolg mit diesem Source!

' Projektversion:   Visual Studio 2005
' Option Strict:    An
' Option Infer:     An
'
' Referenzen: 
'  - System
'  - System.Drawing
'  - System.Windows.Forms
'
' Imports: 
'  - Microsoft.VisualBasic
'  - Microsoft.VisualBasic.ControlChars
'  - System
'  - System.Windows.Forms
'

' ##############################################################################
' ############################## CrossThread.vb ################################
' ##############################################################################
''' <summary>
''' Stellt Methoden bereit, mit denen ein beliebiger Methoden-Aufruf mit bis zu 
'''  3 Argumenten in einen Nebenthread verlegt werden kann, bzw. aus einem 
'''  Nebenthread in den Hauptthread
''' </summary>
Public Class CrossThread

    Public Shared Sub RunAsync(Of T1, T2, T3)( _
        ByVal Action As Action(Of T1, T2, T3), ByVal Arg1 As T1, _
        ByVal Arg2 As T2, ByVal Arg3 As T3)

        ' Aufruf von Action.EndInvoke() gewährleisten, indem er als Callback-
        '  Argument mitgegeben wird
        Action.BeginInvoke(Arg1, Arg2, Arg3, _
            New AsyncCallback(AddressOf Action.EndInvoke), Nothing)
    End Sub

    Public Shared Sub RunAsync(Of T1, T2)( _
        ByVal Action As Action(Of T1, T2), ByVal Arg1 As T1, ByVal Arg2 As T2)

        Action.BeginInvoke(Arg1, Arg2, _
            New AsyncCallback(AddressOf Action.EndInvoke), Nothing)
    End Sub

    Public Shared Sub RunAsync(Of T1)(ByVal Action As Action(Of T1), _
        ByVal Arg1 As T1)

        Action.BeginInvoke(Arg1, _
            New AsyncCallback(AddressOf Action.EndInvoke), Nothing)
    End Sub

    Public Shared Sub RunAsync(ByVal Action As Action)
        Action.BeginInvoke(New AsyncCallback(AddressOf Action.EndInvoke), _
            Nothing)
    End Sub

    Private Shared Function GuiCrossInvoke(ByVal Action As [Delegate], _
        ByVal ParamArray Args() As Object) As Boolean

        If Application.OpenForms.Count = 0 Then
            ' Wenn keine Form mehr da ist, so tun, als ob das Invoking 
            '  ausgeführt wäre
            Return True
        End If

        If Application.OpenForms(0).InvokeRequired Then
            Application.OpenForms(0).BeginInvoke(Action, Args)
            Return True
        End If
    End Function

    Public Shared Sub RunGui(Of T1, T2, T3)( _
        ByVal Action As Action(Of T1, T2, T3), ByVal Arg1 As T1, _
        ByVal Arg2 As T2, ByVal Arg3 As T3)

        ' Wenn kein Invoking erforderlich ist, den Action-Delegaten 
        '  direkt ausführen
        If Not GuiCrossInvoke(Action, Arg1, Arg2, Arg3) Then _
            Action(Arg1, Arg2, Arg3)
    End Sub

    Public Shared Sub RunGui(Of T1, T2)(ByVal Action As Action(Of T1, T2), _
        ByVal Arg1 As T1, ByVal Arg2 As T2)

        If Not GuiCrossInvoke(Action, Arg1, Arg2) Then Action(Arg1, Arg2)
    End Sub

    Public Shared Sub RunGui(Of T1)(ByVal Action As Action(Of T1), _
        ByVal Arg1 As T1)

        If Not GuiCrossInvoke(Action, Arg1) Then Action(Arg1)
    End Sub

    Public Shared Sub RunGui(ByVal Action As Action)
        If Not GuiCrossInvoke(Action) Then Action()
    End Sub

End Class

' ##############################################################################
' ########################### frmAsyncStreaming.vb #############################
' ##############################################################################
Imports System.IO

Public Class frmAsyncStreaming
    Private Const _Output As String = "Output"
    Private Const _Src As String = "Output\TestSource.Data"
    Private Const _Dest As String = "Output\TestDest"

    Private Sub CreateTestSource()
        Me.RichTextBox1.AppendText("erstelle TestSource - bitte warten" & _
            Lf & Lf)

        ' Der Using-Block ist zeitaufwändig und friert das Gui ein. Vorher 
        '  die Neuzeichnung der Richtextbox explizit erzwingen
        Me.RichTextBox1.Refresh()

        If Not Directory.Exists(_Output) Then Directory.CreateDirectory(_Output)
        ' Output-Ordner im Browser anzeigen
        Diagnostics.Process.Start(_Output)
        Using _
            Reader As New FileStream(Application.ExecutablePath, _
            FileMode.Open, FileAccess.Read), _
            Writer As New FileStream(_Src, FileMode.Create)

            ' Große Datei generieren ( > 30MB)
            For I As Integer = 0 To 999
                Reader.Position = 0
                StreamX.WriteTo(Reader, Writer)
            Next
        End Using
    End Sub

    Private Sub Button_Click(ByVal sender As Object, ByVal e As EventArgs) _
        Handles btStreamBegin.Click, btDelegatBegin.Click, _
        btExplicitThread.Click, btCopySynchron.Click, btBackgroundworker.Click

        Static ID As Integer
        ID += 1
        Dim Dest As String = _Dest & ID
        Dim Cause As String = ""
        Dim sFormat As String = "{0,-14:HH:mm:ss.FFF} starte {1} -> {2}"

        If Not File.Exists(_Src) Then CreateTestSource()
            Select Case True
                Case sender Is btStreamBegin
                    Dest &= ".StreamBeginRead"
                    Cause = "Stream.BeginRead()"
                    CopyFileMS(_Src, Dest)

                Case sender Is btDelegatBegin
                    ' Mein Favorit - keine zusätzlichen Methoden erforderlich 
                    '  (bzw. die zusätzlichen Methoden sind allgemein 
                    '  wiederverwendbar)
                    Cause = "Delegate-Invoking"
                    Dest &= ".Invoking"
                    CrossThread.RunAsync(AddressOf CopyFile, Cause, _Src, Dest)

                Case sender Is btExplicitThread
                    Cause = "explicit Threading"
                    Dest &= ".Threading"
                    Dim Tr As New Threading.Thread( _
                        AddressOf ExplicitThreadStart)
                    Tr.Start(New String() {Cause, _Src, Dest})

                Case sender Is btBackgroundworker
                    Dest &= ".Backgroundworker"

                    ' BackgroundWorker hat die Eigenart, daß er nur einen Job 
                    '  erledigen kann 
                    If BackgroundWorker1.IsBusy Then
                        Cause = "BackgroundWorker1.IsBusy = True " & _
                            "- Kann grad nicht"
                        Exit Select
                    End If
                    Cause = "Backgroundworker"
                    BackgroundWorker1.RunWorkerAsync(New String() _
                        {Cause, _Src, Dest})

                Case sender Is btCopySynchron
                    Cause = "synchrones kopieren"
                    Dest &= ".Synchron"
                    WriteLine(String.Format(sFormat, Now, _
                        Cause, Path.GetFileName(Dest)))
                    CopyFile(Cause, _Src, Dest)
                    Return
        End Select
        ' Diese Meldung wird **vor** den Erfolgsmeldungen aus den 
        ' Nebenthreads angezeigt.
        WriteLine(String.Format(sFormat, Now, Cause, Path.GetFileName(Dest)))
    End Sub

    Private Sub ExplicitThreadStart(ByVal Obj As Object)
        Dim Args As String() = DirectCast(Obj, String())
        CopyFile(Args(0), Args(1), Args(2))
    End Sub

    Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, _
        ByVal e As System.ComponentModel.DoWorkEventArgs) _
        Handles BackgroundWorker1.DoWork

        ' Der Vollständigkeit halber hier auch die Verwendung des 
        ' BackgroundWorkers. Dieser bietet noch zusätzlich das Event 
        ' _RunWorkerCompleted, welches hier aber nicht genutzt wird, da 
        ' CopyFile schon einen threadsicheren Mechanismus implementiert, um die 
        '  Erfolgsmeldung anzuzeigen
        Dim Args As String() = DirectCast(e.Argument, String())
        CopyFile(Args(0), Args(1), Args(2))
    End Sub

    Private Sub CopyFile(ByVal Cause As String, ByVal Src As String, _
        ByVal Dest As String)

        ' Nur das Threading mit Stream.BeginRead kann diese Methode in dieser 
        '  Form nicht verwenden
        Using Reader As New FileStream(Src, _
            FileMode.Open, FileAccess.Read, FileShare.Read), _
            Writer As New FileStream(Dest, FileMode.Create)

            StreamX.WriteTo(Reader, Writer)
        End Using
        WriteLine(String.Format("{0,-14:HH:mm:ss.FFF} beende {1} ({2})", _
            Now, Cause, Path.GetFileName(Dest)))
    End Sub

    Private Sub WriteLine(ByVal Text As String)
        CrossThread.RunGui(AddressOf Me.RichTextBox1.AppendText, Text & Lf)
    End Sub

#Region "Asynchroner Kopiervorgang in der von MS vorgesehenen Umständlichkeit"

    Private Structure AsyncInfo
        Public Reader As Stream
        Public Buffer As Byte()
        Public WriterName As String

        Public Sub New(ByVal Reader As Stream, _
            ByVal Buffer As Byte(), ByVal WriterName As String)

            Me.Reader = Reader
            Me.Buffer = Buffer
            Me.WriterName = WriterName
        End Sub
    End Structure

    Private Sub CopyFileMS(ByVal Src As String, ByVal Dest As String)
        Dim Buf(Short.MaxValue - 1) As Byte
        Dim Reader As New FileStream( _
            Src, FileMode.Open, FileAccess.Read, FileShare.Read, _
            Buf.Length, FileOptions.Asynchronous)
        Reader.BeginRead( _
            Buf, 0, Buf.Length, AddressOf Callback, _
            New AsyncInfo(Reader, Buf, Dest))
    End Sub

    Private Sub Callback(ByVal Ar As IAsyncResult)
        With DirectCast(Ar.AsyncState, AsyncInfo)
            ' Suboptimales Design: Der Read-Stream muss in einer anderen 
            '  Methode und in einem anderen Thread disposed werden, als in dem 
            '  er erstellt wurde

            Using Reader As Stream = _
                .Reader, Writer As New FileStream(.WriterName, FileMode.Create)

                Dim Portion As Integer = Reader.EndRead(Ar)
                Writer.Write(.Buffer, 0, Portion)
                While Portion = .Buffer.Length
                    Portion = Reader.Read(.Buffer, 0, .Buffer.Length)
                    Writer.Write(.Buffer, 0, Portion)
                End While
            End Using
        End With
        WriteLine(String.Format("{0,-14:HH:mm:ss.FFF} beende " & _
            "Stream.EndRead()", Now))
    End Sub

    'Asynchroner Kopiervorgang in der von MS vorgesehenen Umständlichkeit
#End Region

    Private Sub frmAsyncStreaming_FormClosed(ByVal sender As Object, _
        ByVal e As FormClosedEventArgs) Handles MyBase.FormClosed

        ' Das schließt kurioserweise den Browser (ich glaub, der stürzt ab)
        Directory.Delete(_Output, True)
    End Sub

End Class

' ##############################################################################
' ################################ StreamX.vb ##################################
' ##############################################################################
Imports System.Runtime.CompilerServices
Imports System.IO

Public Module StreamX
    ' Die Warnung ist zwar keine Summary, aber so wichtig, dass sie 
    '  in der Intellisense angezeigt werden soll
    ''' <summary>
    ''' Achtung! Bei endlosem ReadStream (z.B. System.Net.Sockets.NetworkStream) 
    '''  ist die Angabe von Count **erforderlich**, nicht optional!
    ''' </summary>
    Public Sub WriteTo( _
        ByVal ReadStream As Stream, _
        ByVal WriteStream As Stream, _
        Optional ByVal Count As Long = Long.MaxValue, _
        Optional ByVal Bufsize As Integer = Short.MaxValue)

        ' Hinweis für VB2008-Nutzer: Diese Sub ist eigentlich als 
        '  Extension der Stream-Klasse gedacht
        Dim Buf(Bufsize - 1) As Byte
        Dim ReadCount As Long = 0
        While Count > ReadCount
            Dim Portion As Integer = _
                ReadStream.Read(Buf, 0, _
                    CInt(Math.Min(Count - ReadCount, Bufsize)))
            If Portion = 0 Then
                If Count = Long.MaxValue Then Return
                Throw New ArgumentException(String.Concat( _
                    "StreamX.WriteTo(ReadStream, WriteStream, Count:=", _
                    Count, "): ReadStream konnte ", "nur ", ReadCount, _
                    " Bytes lesen"))
            End If
            ReadCount += Portion
            WriteStream.Write(Buf, 0, Portion)
        End While
    End Sub
End Module
' ##############################################################################
' ################################# System.vb ##################################
' ##############################################################################
Namespace System
    ' Hinweis für VB2008-Nutzer: Diese 3 Delegaten sind in 
    '  System.Core bereits vordefiniert
    Public Delegate Sub Action()
    Public Delegate Sub Action(Of T1, T2) _
        (ByVal Arg1 As T1, ByVal Arg2 As T2)
    Public Delegate Sub Action(Of T1, T2, T3) _
        (ByVal Arg1 As T1, ByVal Arg2 As T2, ByVal Arg3 As T3)
End Namespace

Ihre Meinung  

Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.

Archivierte Nutzerkommentare 

Klicken Sie diesen Text an, wenn Sie die 4 archivierten Kommentare ansehen möchten.
Diese stammen noch von der Zeit, als es noch keine direkte Forenunterstützung für Fragen und Kommentare zu einzelnen Artikeln gab.
Aus Gründen der Vollständigkeit können Sie sich die ausgeblendeten Kommentare zu diesem Artikel aber gerne weiterhin ansehen.

Kommentar von Eckard Ahlers am 27.04.2010 um 00:39

Normalerweise sind bei DateiOperationen die Daten ja sofort da, also ruft das OS sofort zurück, und der thread ist belegt.
Evtl ist .BeginRead für NetStreams von Interesse.
Mir jedenfalls ist es bis jetzt nicht gelungen, eine Umgebung zu schreiben, die genanntes Verhalten demonstriert, und vorteile zB. gegenüber dem Verhalten des threadPools (delegate.BeginInvoke) herausarbeitet.

Kommentar von Dario am 26.04.2010 um 12:19

Das wesentliche an asynchronen Dateioperationen ist ja, dass sie keinen Thread blockieren. Man kann immer nur eine begrenzte Zahl von Threads erstellen und wirklich effizient ist das eh nur, wenn sie sich bspw. auf mehrere Kerne verteilen. Das geniale an Begin/End ist ja eben, dass sie keinen Arbeitsthread belegen, sondern das Betriebssystem einfach "zurückruft", wenn es Daten gibt.

Kommentar von Eckard Ahlers am 11.11.2007 um 13:33

Hmm.

Das wesentliche am Streaming, nämlich, daß der Stream häppchenweise in einen Buffer gelesen wird, ist nicht implementiert.
Dabei ist grade das bei asynchronen Dateizugriffen besonders kniffelig.
Um 1000 Zeichen zu lesen oder zu schreiben würde man doch nie Threading einsetzen.

Kommentar von willibrord oomen am 18.04.2005 um 19:49

Friend Sub UserDocument_AsyncReadProgress(AsyncProp As AsyncProperty)

If AsyncProp.StatusCode = vbAsyncStatusCodeConnecting then

[how to write to code to unload this userdocument ?]

End Sub


ps: ich lese deutsch aber kann es nich rechtschreiben ... entschüldigen sie mich bitte.