Async Processing and ApplicationContext/ClientContext

Async Processing and ApplicationContext/ClientContext

Old forum URL: forums.lhotka.net/forums/t/10703.aspx


NemisisJedi posted on Wednesday, September 21, 2011

Hi guys,

Most of our code uses sync processing, but i have recently created an object that calls a Build method, in this Build method, the code loops through some children objects and builds them in a List property against the object.  This works perfectly if i do it sync'ly, but its takes a while to complete, so thought running each child async would speed things up.

I have wrote the following code, but I hold the Identity object in the ApplicationContext and this seems to be blank when the sync threads run, and i need this for each child object as i read certain values in there.

I have wrote the following code so you can run the Build method, so code runs sync'ly, or you can call BuildAsync, to run it async'ly.  If you run the Async method, then it should raise the BuildCompleted event.

The following code doesnt use the DataPortal.BeginExecute code as i am not sure if and how i can use this for this.  If it is better to use this, then please let me know, the DataPortal may fix my ApplicationContext/ClientContext problem??

Any suggestions are welcome, i have done some async method before, but i have always created the Identity object (in ApplicationContext) in the sync thread.

Here is my example code against the parent object

Public Class ParentObject

        Private mBuildCount As Integer
        Private WithEvents mReport As ChildObject

        Public Overrides Sub BuildAsync()

            If Not IsAsyncRunning Then

                IsAsyncRunning = True

                ' get my child objects, sync'ly
                Dim mBuiltReports As List(Of ChildObjects) = GetReports()

                ' set buildcount = 0
                mBuildCount = 0

                ' loop through each report and start building
                Dim lLoopCount As Integer = 0

                While lLoopCount < mBuiltReports.Count

                    ' set to mReport so we can call the reports BuildCompleted event
                    mReport = mBuiltReports.Values(lLoopCount)
                    mReport.BuildAsync()

                    lLoopCount += 1
                End While

            End If

        End Sub

        Private Sub Report_BuildCompleted(ByVal sender As Object, ByVal e As ComponentModel.AsyncCompletedEventArgs) Handles mReport.BuildCompleted
            Dim lReport As IBaseReport = DirectCast(sender, IBaseReport)
            Dim lBuildCount As Integer

            mBuiltReports(lReport.ReportId) = lReport

            ' lock this object to update this value
            SyncLock Me
                mBuildCount += 1
                lBuildCount = mBuildCount
            End SyncLock

            ' all reports are built so raise build completed event
            If lBuildCount = mBuiltReports.Count Then
                OnBuildCompleted(Me, New ComponentModel.AsyncCompletedEventArgs(Nothing, False, Nothing))
            End If

        End Sub

End Class

Public Class ChildObject

        Private Delegate Sub BuildDelegate()
        Public IsAsyncRunning As Boolean

        Public Overridable Sub BuildAsync() Implements IBaseReport.BuildAsync
            If Not IsAsyncRunning Then
                IsAsyncRunning = True

                Dim lBuildDelegate As New BuildDelegate(AddressOf Build)
                Dim lCallback As New AsyncCallback(AddressOf BuildCallBack)
                Dim lAsync As AsyncOperation = AsyncOperationManager.CreateOperation(Nothing)

                lBuildDelegate.BeginInvoke(lCallback, lAsync)
            End If
        End Sub

        Private Sub BuildCallBack(ByVal ar As IAsyncResult)

            ' Retrieve the delegate.
            Dim lDelegate As BuildDelegate = CType(ar.AsyncState, BuildDelegate)
            Dim lAsync As AsyncOperation = CType(ar.AsyncState, AsyncOperation)

            ' Call EndInvoke to retrieve the results.
            lDelegate.EndInvoke(ar)

            ' raise event to say method is completed
            Dim lCompletedArgs As New AsyncCompletedEventArgs(Nothing, False, Nothing)

            ' call sub to raise event
            OnBuildCompleted(Me, lCompletedArgs)

            ' set as not running
            IsAsyncRunning = False

        End Sub

        Public Event BuildCompleted As AsyncCompletedEventHandler Implements IBaseReport.BuildCompleted

        Protected Overridable Sub OnBuildCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
            RaiseEvent BuildCompleted(Me, e)
        End Sub

        Public Sub Build()
                 ' This method loops through the child objects and calls there BuildAsync method
                 ' so that they are built
                 For Each lChild As ChildObject In Me.Children
                          lChild.BuildAsync()

                 Next
        End Sub

End Class

JonnyBee replied on Wednesday, September 21, 2011

Hi,

If your async method does a DataPortal call to get data7build object then Iæd recommend to use the async dataportal.

Second option is to use the Csla.Threading.BackgroundWorker. This background worker
is wrapper on top of System.ComponentModel.BackgroundWorker that will make sure to
transfer ApplicationContext and Principal to the background thread.

 

 

NemisisJedi replied on Wednesday, September 21, 2011

The csla.threading.backgroundworker sounds perfect, I guess u can fire events on completion etc? Would u happen to have some basic sample code or point me to some?

JonnyBee replied on Wednesday, September 21, 2011

Works just the same as System.ComponentModel.BackgroundWorker

Basically:

DoWork -this code runs on a background thread

RunWorkerCompleted - the callback on the "main" thread. Always check for Error before you access the Object.

Start processing by calling BackgroundWorker.RunWorkerAsync(object parameter).

There is also a demo project in Samples\Net\cs\BackgroundWorkerDemo, the samples download for Csla 4.1

 

NemisisJedi replied on Wednesday, September 21, 2011

Jonny You seem to know a lot about asynchronous processing, so my last question should be simple for you In the function that calls BackgroundWorker.RunWorkerAsync, what is the best way to wait for the callback event? At the moment I am doing threading.wait which doesn't seem right to me. Sry if this is a stupid question

JonnyBee replied on Wednesday, September 21, 2011

Hmmm, I'd probably add another level of "abstraction" and have another Backgroundworker on top.

You use BackgroundWorkers to offload the fetch from the UI thread and you do NOT want to have Thread.Wait or other tricks on that thread.

So I'd rather have a background worker - to offload from the UI thread and start the other background workers in DoWork. And use a WaitHandle to halt execution (exit DoWork)  until the last backgroundworker has completed. You could even support Cancelling of the async operations.

http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx

WaitHandle's is also used in the unit testing of Csla so look at the unit tests.

This is going to be a lot easier with the new async/await keywords.Smile

Copyright (c) Marimer LLC