Rob Ainscough
Rob Ainscough

Reputation: 617

How to use Async/Await in Silverlight 5 (VB)

I'm trying to call a web service and have my code wait for that service to return a result (or timeout). My project is Silverlight 5 with Web Services using .NET 4.0 and I'm running this project under VS 2012 with the Microsoft.Bcl.Async.1.0.16\lib\sl4\Microsoft.Threading.Tasks.dll ... Task.Extensions.dll ... and Extensions.Silverlight.dll.

This is how I've been doing it and it's working, but I'm trying to figure out how to change my code so that I can use the Async/Await process. The web service reference is configured to return ObservableCollection and Generic.Dictionary with Reuse types in all referenced assemblies.

Some of my code I need to convert to Async/Await:

    Private _Units As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
Public Property Units() As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
    Get
        Return _Units
    End Get
    Set(ByVal value As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
        _Units = value
        OnPropertyChanged(New PropertyChangedEventArgs("Units"))
    End Set
End Property


   Public Sub ReadUnits()

    Try

        ' Client is required
        If Not Me.Client Is Nothing Then

            ' User is required
            If Not Me.User Is Nothing Then

                ' Must be a real Client
                If Me.Client.ClientID > 0 Then

                    ' My have a sites
                    If Not Me.Site Is Nothing Then

                        ' Call the web service relative to where this application is running
                        Dim webServiceURI As New Uri("../WebServices/Unit.svc", UriKind.RelativeOrAbsolute)
                        Dim webServiceAddress As New EndpointAddress(webServiceURI)

                        ' Setup web Service proxy
                        Dim wsUnits As New DC.SL.Services.WebServiceUnit.UnitClient
                        wsUnits.Endpoint.Address = webServiceAddress

                        ' Add event handler so we can trap for web service completion
                        AddHandler wsUnits.LoadsCompleted, AddressOf LoadUnitsCompleted

                        ' Call web service to get Sites the user has access to
                        wsUnits.LoadsAsync(Me.Client, Me.Site.SiteID, Me.Size.SizeID, Me.RentalType.RentalTypeID, Me.UnitState)

                    End If

                End If

            End If

        End If

    Catch ex As Exception

        Dim Problem As New DC.SL.Tools.Errors(ex)

    End Try

End Sub

Private Sub LoadUnitsCompleted(ByVal sender As Object, ByVal e As DC.SL.Services.WebServiceUnit.LoadsCompletedEventArgs)

    Try

        If Not IsNothing(e.Result) Then

            Me.Units = e.Result

            If Me.Units.Count > 0 Then
                Me.Unit = Me.Units.Item(0)
            End If

        End If

    Catch ex As Exception

        Dim Problem As New DC.SL.Tools.Errors(ex)

    End Try

End Sub

Still not getting this to work ... here is what I have now, but the problem remains ... UI thread execution continues and does NOT wait for the Web Service call to finish.

Calling code:

ReadUnitsAsync().Wait(3000)

Here is the updated code:

    Public Async Function ReadUnitsAsync() As Task(Of Boolean)

    Dim Results As Object = Await LoadReadUnitsAsync()

    Return True

End Function

Public Function LoadReadUnitsAsync() As Task(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))

    LoadReadUnitsAsync = Nothing

    Dim tcs = New TaskCompletionSource(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))

    ' Client is required
    If Not Me.Client Is Nothing Then

        ' User is required
        If Not Me.User Is Nothing Then

            ' Must be a real Client associated
            If Me.Client.ClientID > 0 Then

                ' Only get associated sites IF we don't have any defined
                If Not Me.Site Is Nothing Then

                    ' Call the web service relative to where this application is running
                    Dim webServiceURI As New Uri("../WebServices/Unit.svc", UriKind.RelativeOrAbsolute)
                    Dim webServiceAddress As New EndpointAddress(webServiceURI)

                    ' Setup Site web Service proxy
                    Dim wsUnits As New DC.SL.Services.WebServiceUnit.UnitClient
                    wsUnits.Endpoint.Address = webServiceAddress

                    ' Add event handler so we can trap for web service completion
                    AddHandler wsUnits.LoadUnitsCompleted, Sub(s, e)
                                                               If e.Error IsNot Nothing Then
                                                                   tcs.TrySetException(e.Error)
                                                               ElseIf e.Cancelled Then
                                                                   tcs.TrySetCanceled()
                                                               Else
                                                                   tcs.TrySetResult(e.Result)
                                                               End If
                                                           End Sub

                    '' Set Busy Status
                    'BusyStack.Manage(ProcessManager.StackAction.Add, "ReadUnits", Me.IsWorking, Me.IsWorkingMessage)

                    ' Call web service to get Sites the user has access to
                    wsUnits.LoadUnitsAsync(Me.Client, Me.Site.SiteID, Me.Size.SizeID, Me.RentalType.RentalTypeID, Me.UnitState)

                    Return tcs.Task

                End If

            End If

        End If

    End If

End Function

So here is the final code (abbreviated) that seems to be working to my goals (aka waiting for a Web Service to finish before proceeding).

Public Class UIUnits
Implements INotifyPropertyChanged, IDataErrorInfo

Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

Public Async Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)

    Dim propertyEventHandler As PropertyChangedEventHandler = PropertyChangedEvent

    Try

        If propertyEventHandler IsNot Nothing Then

            RaiseEvent PropertyChanged(Me, e)

            Select Case e.PropertyName

                Case "Size"

                    Await ReadUnitsAsync()

        DoSomethingElseAfterWebServiceCallCompletes()

                Case Else

            End Select

        End If

    Catch ex As Exception

        Dim problem As New DC.SL.Tools.Errors(ex)

    End Try

End Sub

...

Private _Units As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
Public Property Units() As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units)
    Get
        Return _Units
    End Get
    Set(ByVal value As Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))
        _Units = value
        OnPropertyChanged(New PropertyChangedEventArgs("Units"))
    End Set
End Property

...

Public Async Function ReadUnitsAsync() As Task(Of Boolean)

    Me.Units = Await LoadReadUnitsAsync()
    Return True

End Function

...

Public Function LoadReadUnitsAsync() As Task(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))

    LoadReadUnitsAsync = Nothing

    Dim tcs = New TaskCompletionSource(Of System.Collections.ObjectModel.ObservableCollection(Of DC.SL.Services.WebServiceUnit.Units))

    ' Client is required
    If Not Me.Client Is Nothing Then

        ' User is required
        If Not Me.User Is Nothing Then

            ' Must be a real Client associated
            If Me.Client.ClientID > 0 Then

                ' Only get associated sites IF we don't have any defined
                If Not Me.Site Is Nothing Then

                    ' Call the web service relative to where this application is running
                    Dim webServiceURI As New Uri("../WebServices/Unit.svc", UriKind.RelativeOrAbsolute)
                    Dim webServiceAddress As New EndpointAddress(webServiceURI)

                    ' Setup web Service proxy
                    Dim wsUnits As New DC.SL.Services.WebServiceUnit.UnitClient
                    wsUnits.Endpoint.Address = webServiceAddress

                    ' Add event handler so we can trap for web service completion
                    AddHandler wsUnits.LoadUnitsCompleted, Sub(s, e)
                                                               If e.Error IsNot Nothing Then
                                                                   tcs.TrySetException(e.Error)
                                                               ElseIf e.Cancelled Then
                                                                   tcs.TrySetCanceled()
                                                               Else
                                                                   tcs.TrySetResult(e.Result)
                                                               End If
                                                           End Sub

                    ' Call web service 
                    wsUnits.LoadUnitsAsync(Me.Client, Me.Site.SiteID, Me.Size.SizeID, Me.RentalType.RentalTypeID, Me.UnitState)

                    Return tcs.Task

                End If

            End If

        End If

    End If

End Function

Upvotes: 1

Views: 2319

Answers (2)

Xelom
Xelom

Reputation: 1625

I used this library to achieve async await in my Silverlight 5 project

Upvotes: 2

Stephen Cleary
Stephen Cleary

Reputation: 456937

In this case it's probably easiest to convert from the lowest level and work your way up. First, you need to define your own TAP-friendly extension methods on your service. VS will generate these for you if you are doing desktop development, but unfortunately it will not do this for Silverlight.

The MSDN docs describe how to wrap EAP (EAP is the pattern that uses *Async methods with matching *Completed events). If you have APM methods, it's even easier to wrap those into TAP (APM is the pattern that uses Begin*/End* method pairs).

Once you have a wrapper, e.g., LoadUnitsTaskAsync, change your method to call that instead of LoadUnitsAsync and Await the result. This will require your ReadUnits method to be Async, so change it to a Task-returning Function (and change its name from ReadUnits to ReadUnitsAsync). Next change all callers of ReadUnitsAsync so they Await its result. Repeat until you reach an actual event handler, which may be Async Sub (do not use Async Sub for any intermediate methods; use Async Function ... As Task instead).

Upvotes: 3

Related Questions