Visual Basic 2012 Voorbeelden
   

visual basic 2012 broncode voorbeelden

Blijf op de hoogte van de recente aanpassingen op vbvoorbeelden!

Microsoft Visual Studio 2012Microsoft Developers Network - Visual BasicMicrosoft .NET Framework

35.3. Asynchrone Code - Async en Await

Print Email Deel op Twitter Deel op Facebook

Dit artikel is gepubliceerd op maandag 15 oktober 2012 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.

Asynchroon programmeren staat ons toe de mate van responsiviteit van onze applicaties hoog te houden.  Zeker bij zware input/output of processor operaties willen we vermijden dat terwijl de tijdrovende operatie wordt uitgevoerd onze applicatie lijkt vast te lopen.  Het is bijvoorbeeld niet aanvaardbaar dat onze applicatie niet reageert op zaken als toetsenbord- of muishandelingen.

Vanaf Visual Basic 2012 (11.0) kunnen we gebruik maken van een vereenvoudigde benadering van asynchroon programmatie die gebruik maakt van bepaalde ondersteuning die voorzien is vanaf .NET Framework 4.5 en ook de Windows Runtime (WinRT, Metro style applicaties).
De compiler gaat voor ons het moeilijke werk doen terwijl onze code eenvoudig en leesbaar blijft.

35.3.1. Control Flow

Een Async method is ideaal om potentieel tijdrovende operaties in uit te voeren zonder dat de thread waarop de calling method wordt uitgevoerd hierdoor geblokkeerd wordt.  In volgende codeweergave hebben we drie Async methods: de in het .NET Framework voorgedefinieerde SendPingAsync en onze eigen PingStatusAsync functie en Button2_Click eventhandler:
Async Sub Button2_Click() Handles Button2.Click                               ' (1)
    Dim pingStatusTask As Task(Of String) = PingStatusAsync("209.85.231.104") ' (2) (6)
    Label1.Text = "Start pinging..."                                          ' (7)
    Dim pingStatus As String = Await pingStatusTask                           ' (8) (ui) (12)
    Label1.Text = pingStatus                                                  ' (13)
End Sub                                                                       ' (14)

Async Function PingStatusAsync(ipAddress As String) As Task(Of String)        ' (3)
    Dim ping As New Ping                                                      ' (4)
    Dim pingReply As PingReply = Await ping.SendPingAsync(ipAddress)          ' (5) (9)
    Return pingReply.Status.ToString()                                        ' (10)
End Function                                                                  ' (11)
Bij het klikken op Button2 (1) wordt de method PingStatusAsync aangeroepen (2)(3), die op zijn beurt op een object van type Ping (4) SendPingAsync aanroept (5).  SendPingAsync zal een bepaald IP adres pingen en na afloop het ping resultaat (PingReply.Success, PingReply.TimedOut, ...) opleveren (10).  Pingen is een tijdrovende operatie, waarmee we het risico lopen dat de verdere uitvoering van PingStatusAsync, en op zijn beurt eventhandler Button2_Click, wordt opgehouden.  Als de uitvoering van de eventhandler wordt opgehouden dan lijkt onze userinterface te bevriezen.

Om dit te vermijden, roepen we SendPingAsync asynchroon aan door gebruik te maken van de Await operator (5).  Het pingen wordt alvast gestart.  Dit gebeurt op een aparte workerthread omdat SendPingAsync nu eenmaal zo is geïmplementeerd.  En de activatie gaat opnieuw over naar de aanroepende method PingStatusAsync (5).
Deze heeft op zijn beurt met Await aan dat het zelf niet verder kan, en dat het de activatie zal overdragen naar zijn aanroepende method (Button2_Click) (6).  PingStatusAsync kon zelf niet verder want het door SendPingAsync te resulteren PingReply object is nodig voor het bepalen van de String return waarde (10).

Eens de activatie opnieuw bij Button2_Click is terecht gekomen (6) kan deze eventhandler op de userinterface signaleren dat het pingen gestart is (7).  De volgende zinnige operatie voor onze eventhandler is het weergeven van het ping resultaat op Label1 (13).  Maar om dat te kunnen doen moet Button2_Click het voltooien van de uitvoering van method PingStatusAsync afwachten.  Dit is wat het keyword Await op regel (8) dan ook aangeeft.
Bij Await wordt opnieuw de activatie overgedragen naar de aanroepende code.  Dit zorgt ervoor dat de userinterface thread verder kan gaan (ui), en verdere gebeurtenissen (bijvoorbeeld afkomstig van de gebruiker via toetsenbord of muis) ook netjes worden afgehandeld.  De userinterface blijft dus responsief omdat Button2_Click de userinterface thread niet blokkeert.

Bemerk hoe Async Function PingStatusAsync een invulling van Task(Of TResult) oplevert en zo eigenlijk belooft bij voltooien (Await) een String waarde op te leveren.  In een Task(Of TResult) Async method zal naast Return een expressie van type TResult staan (10).
De opgeleverde Task is een object dat representeert wat nog moet gebeuren in de opgeschorte Async method.
Properties van het opgeleverde Task object houden informatie zoals de status van de taak, eventueel in de taak opgetreden excepties en het uiteindelijk resultaat van de taak.  De Await operator maakt gebruik van deze properties.
Een Task wordt wel eens omschreven als een belofte om uiteindelijk een waarde op te leveren, terwijl ondertussen de calling method verder werk kan voltooien die niet afhankelijk is van de beloofde waarde (7).
Vaak wordt voltooiing van een aanroep naar een asynchrone method meteen afgewacht, de instructies:
Dim pingStatusTask As Task(Of String) = PingStatusAsync("209.85.231.104")
Dim pingStatus As String = Await pingStatusTask
Hadden we dan ook net zo goed op één regel kunnen coderen:
Dim pingStatus As String = Await PingStatusAsync("209.85.231.104")
Net als we de voltooiing van de aanroep naar de SendPingAsync method ook meteen afwachten:
Dim pingReply As PingReply = Await ping.SendPingAsync(ipAddress)
Ook daar was het mogelijk geweest om eerst het door SendPingAsync opgeleverde Task(Of PingReply) object op te vangen:
Dim pingReplyTask As Task(Of PingReply) = ping.SendPingAsync(ipAddress)
' (*)
Dim pingReply As PingReply = Await pingReplyTask
(*) Dit levert de mogelijkheid om tussen het starten van de Async method en het opschorten van de calling method (bij Await) voor het afwachten van de voltooiing bepaalde logica uit te voeren.  Net als we in Button2_Click tussen het starten van het pingen en het wachten op de voltooiing van het pingen alvast op de userinterface signaleren dat het pingen gestart is.

Eens het pingen of dus de uitvoering van SendPingAsync voltooid is, wordt van het corresponderen Task(Of PingReply) object de Result property ingesteld, en komt de activatie opnieuw terecht in de aanroepende method PingStatusAsync (9).  De Await expressie evalueert naar de opgeleverde Result waarde, waardoor nu op regel (10) het ping resultaat als String return waarde wordt ingesteld.
Hiermee wordt de Result property van het door PingStatusAsync gecreëerde Task(Of String) object op deze String waarde ingesteld en is de uitvoering van de Async method PingStatusAsync is beëindigd (11).
Hierdoor kan het verder opschorten van de aanroepende eventhandler Button2_Click ook ophouden en zal de Await pingStatusTask expressie in de aanroepende Button2_Click eventhandler evalueren naar het String ping resultaat (12).
Het ping resultaat wordt weergegeven op Label1 (13) en de eventhandler is beëindigd (14).

We hebben nergens in de code zelf Task objecten moeten aanmaken, noch daarin de status van de uitgevoerde taak daarin beheert.  We moeten bijvoorbeeld ook zelf niet de registratie van de callbacks uitschrijven om ervoor te zorgen dat opgeschorte taken weer correct worden opgepikt.
Al het nodige zal de compiler zelf in de MSIL gaan injecteren.  Enkel de pure essentie van de synchronisatie logica moeten we zelf nog gaan uitwerken.  Het simpelweg toevoegen van een Await (en Async) keyword kan volstaan om aan te geven dat een bepaalde uitvoering wordt opgeschort en de control moet worden overgedragen aan de aanroepende method.

35.3.2. Volledig voorbeeld

Om het volledige voorbeeld uit te voeren maak je een Windows Forms Applicatie met op Form1 twee knoppen (Button1 en Button2), een label (Label1) en een uitvallijst (ComboBox1):
Visual Basic 2012 Broncode - Codevoorbeeld 843
Option Strict On
Imports System.Net.NetworkInformation
Public Class Form1
    Private Sub Form1_Load() Handles MyBase.Load
        ComboBox1.Items.Add("209.85.231.104") ' Google.com
        ComboBox1.Items.Add("207.46.170.123") ' Microsoft.com
        ComboBox1.Items.Add("66.220.149.25")  ' Facebook.com
        ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
        ComboBox1.SelectedIndex = 0
        '
        Button1.Text = "Ping"
        Button2.Text = "Ping Async"
        '
        Label1.Text = ""
    End Sub
    Private Sub Button1_Click() Handles Button1.Click
        Dim ipAddress As String = ComboBox1.SelectedItem.ToString()
        '
        Dim network As New Microsoft.VisualBasic.Devices.Network
        Label1.Text &= "Start pinging " & ipAddress & "..." & Environment.NewLine()
        Dim successful As Boolean = network.Ping(ipAddress)
        Dim pingStatus As String = If(succesful, "Successful", "Not successful")
        '
        Label1.Text &= "Ping " & ipAddress & ": " & pingStatus & Environment.NewLine()
    End Sub
    Private Async Sub Button2_Click() Handles Button2.Click
        Dim ipAddress As String = ComboBox1.SelectedItem.ToString()
        '
        Dim pingStatusTask As Task(Of String) = PingStatusAsync(ipAddress)
        Label1.Text &= "Start pinging " & ipAddress & "..." & Environment.NewLine()
        Dim pingStatus As String = Await pingStatusTask
        '
        Label1.Text &= "Ping " & ipAddress & ": " & pingStatus & Environment.NewLine()
    End Sub
    Private Async Function PingStatusAsync(ipAddress As String) As Task(Of String)
        Dim ping As New Ping
        '
        Dim pingReplyTask As Task(Of PingReply) = ping.SendPingAsync(ipAddress)
        Dim pingReply As PingReply = Await pingReplyTask
        'or immediat await: Dim pingReply As PingReply = Await ping.SendPingAsync(ipAddress)
        '
        Return pingReply.Status.ToString()
    End Function
End Class
In het voorbeeld wordt de asynchrone eventhandler Button2_Click vergeleken met de synchrone Button1_Click.

Bemerk bij het uitvoeren dat bij het klikken op Button1 de userinterface eventjes geblokkeerd is.

Button1_Click maakt gebruik van de Network.Ping method die niet asychroon kan worden aangeroepen.  Hierdoor blijft deze eventhandler wachten op het voltooien van het pingen, maar wordt de control ook niet teruggeven aan de aanroepende code.  Hierdoor blijft de userinterface thread bezig met het afwachten en worden andere userinterface events niet meteen afgehandeld.

Voorzien van de mogelijkheid van het asynchroon aanroepen is belangrijk voor potentieel blokkerende operaties.

Typische voorbeelden zijn wanneer er bronnen over het netwerk (internet) worden gebruikt en bij het benaderen bestanden of databanken.

Om een Await operator in een implementatie van een method te kunnen gebruiken, moet deze method in zijn signatuur als Async gemarkeerd zijn.

Het is een naming guideline om identifiers van asynchrone methods de "Async" suffix te geven.  Dit is ook wat Microsoft heeft gedaan, waardoor we makkelijk herkennen welke methods uit de klassenbibliotheek van het .NET Framework we met Await kunnen aanroepen.
Een uitzondering zijn de Async eventhandlers, Button2_ClickAsync zou niet veel bijbrengen, eventhandlers worden immers doorgaans impliciet aangeroepen.

Async en Await zorgen er op zich niet zelf voor dat extra threads worden aangemaakt.  Multithreading is niet noodzakelijk omdat de uitvoering van Async method verder loopt op de thread waar de aanroepende method op wordt uitgevoerd.  Toch is het zo dat vele asynchrone methods hun tijdrovende operaties op een apart (worker)thread gaan uitvoeren.

Async methods bevatten doorgaans één of meerdere Await expressies, is dit niet het geval dan wordt deze method, ondanks de Async modifier, synchroon uitgevoerd.  De compiler zal hier wel een warning voorzien.

35.3.3. Async Task(Of TResult) Return Type

Vele Async methods hebben Task(Of TResult) als return type.
Async Function MethodAsync() As Task(Of TResult)
   ...some awaited asynchronous call is being made...
   Return ...something of type TResult...
End Function

Dim result As TResult = Await AsyncMethod()
Als een Async method van return type Task(Of TResult) wordt aangeroepen in een Await expressie, zal de Await expressie de waarde van type TResult, die bewaard is in het Task object, opleveren.
Dim resultTask As Task(Of TResult) = MethodAsync()
'work not depending on result can continue...
Dim result As TResult = Await resultTask
'or: Dim result As TResult = resultTask.Result
Een call naar een Async method die niet wordt gemaakt in een Await expressie zal een Task(Of TResult) object opleveren.  Dit Task(Of TResult) object bevat een Result property van type TResult.
De Await expressie zal uiteindelijk, eens de taak volbracht is, evalueren naar de Result property waarde van type TResult.
Het opvragen van Result property zal indien de taak nog niet volbracht is, de actieve thread blokkeren tot de taak voltooid is en de door de property op te leveren waarde beschikbaar is.

35.3.4. Async Task Return Type

Async methods die geen waarde opleveren ("void returning") hebben doorgaans return type Task.  Deze methods zouden in Visual Basic, als ze niet asynchroon worden gebruikt, als Sub worden gedefinieerd.
Async Function MethodAsync() As Task
   ...some awaited asynchronous call is being made...
   ...no value is returned...
End Function

Await AsyncMethod()
Opnieuw kan de calling method een Await operator gebruiken om de Async method aan te roepen, om zo zijn eigen uitvoering te onderbreken tot de Async method voltooid is.
Bemerk dat het deze keer eerder gaat om een Await statement in plaats van een Await expressie.  Het opgeleverde Task object heeft hier dan ook geen Result property om een return waarde in te bewaren.

Er kan ook gewerkt worden met de Task return waarde van deze Async method:
Dim task As Task = MethodAsync()
'work not depending on completion of task can continue...
Await task
Een voorbeeld van een Sub is de Write method van de System.IO.StreamWriter klasse.  Een asynchrone versie bestaat vanaf .NET Framework 4.5 ook, namelijk WriteAsync.

Creëer een Windows Forms Applicatie project en plaats op Form1 een knop (Button1) en een label (Label1) om volgend voorbeeld uit te voeren.
Visual Basic 2012 Broncode - Codevoorbeeld 844
Imports System.IO
Public Class Form1
    Private Async Sub Button1_Click() Handles Button1.Click
        Dim sw As StreamWriter
        Try
            sw = File.CreateText("helloworld.txt")
            '
            Dim writeTask As Task = sw.WriteAsync("Hello, world!")   ' (1)
            Label1.Text = "Writing to file..."
            Await writeTask                                          ' (2)
            Label1.Text = "Done writing."
            '
        Catch ex As Exception
            MessageBox.Show("Some error occured: " & ex.Message)
        Finally
            If sw IsNot Nothing Then sw.Close()
        End Try
    End Sub
End Class

35.3.5. Async Subroutines

Eventhandlers zijn vaak het startpunt van een stuk asynchrone logica.  Een eventhandlers is altijd een subroutine (Sub), ook een Sub mag bijgevolg met de Async modifier gemarkeerd worden:
Async Sub EventHandlerAsync(...) Handles SomeEventSource.SomeEvent
    ...some asychronous call is being made...
End Sub
Omdat hier geen Task wordt opgeleverd, is het gevolg dat het voltooien van de uitvoering van een Async eventhandler niet kan worden opgewacht.  Zoiets als Await EventHandlerAsync() is dan ook niet mogelijk.

35.3.6. Excepties van Asynchrone Methods Opvangen

Als een exceptie optreedt bij het voltooien van de uitvoering van een Async method, zal de Await operator de exceptie opnieuw opwerpen:
Visual Basic 2012 Broncode - Codevoorbeeld 845
Public Class Form1
    Private Async Sub Button1_Click() Handles Button1.Click
        Dim task1 As Task = GenerateException()
        Try
            Await task1
        Catch ex As Exception
            Label1.Text = "Exception Message: " &
                          ex.Message & Environment.NewLine() &
                          "Task InnerException Message: " &
                          task1.Exception.InnerException.Message
        End Try
    End Sub
    Private Async Function GenerateException() As Task
        Throw New Exception("Something went wrong.")
    End Function
End Class
Op Label1 krijgen we:
Exception Message: Something went wrong.
Task InnerException Message: Something went wrong.

35.3.7. Async in Lambda Expression

De Async modifier kan ook in een lambda expressie worden gebruikt:
AddHandler Button2.Click,
           Async Button2_Click()
               Dim ipAddress As String = ComboBox1.SelectedItem.ToString()
               '
               Dim pingStatusTask As Task(Of String) = PingStatusAsync(ipAddress)
               Label1.Text &= "Start pinging " & ipAddress & "..." & Environment.NewLine()
               Dim pingStatus As String = Await pingStatusTask
               '
               Label1.Text &= "Ping " & ipAddress & ": " & pingStatus & Environment.NewLine()
           End Sub

Dit artikel is gepubliceerd op maandag 15 oktober 2012 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.