Objects that are marked busy may not be saved - Async solution

Objects that are marked busy may not be saved - Async solution

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


g.beraudo@castsoftware.com posted on Tuesday, February 26, 2013

Hi,

I'm trying to save a business object which as async rules, which is not bound to a form/windows.

I'm creating the object, filling it, and immediatly trying to save it, in the UI thread.

As a consequence, when saving, IsBusy is still true (my async business rules never completed), and save fails.

I assumed that I need to release the UI thread, to let my async rules return and update my object.

 

Therefire, I tried solving my provlem by overriding SaveAsync, as below.

        Protected Overrides Async Function SaveAsync(forceUpdate As Boolean, userState As Object, isSync As Boolean) As Task(Of T)

            For i As Integer = 0 To 40 'loop to simulate delays,  up to 4s (40 x 100ms)

                If Not Me.IsBusy Then Exit For

                Await TaskEx.Delay(TimeSpan.FromMilliseconds(100))  'this call blocks the UI thread and never return

            Next

            Return Await MyBase.SaveAsync(forceUpdate, userState, isSync)

        End Function

 

I do not understand why the Await TaskEx.Delay is blocking my UI thread.

Has anyone face such issue? Has better alternatives?

I'm using 4.5.11 with local dataportal on .NET 4 and AsyncTargetingPack (on VisualStudio 2012).

Regards,

Gilles

Note there is a similar thread: http://forums.lhotka.net/forums/p/10156/47658.aspx on older version of CSLA / without AsyncTargettingPack

RockfordLhotka replied on Tuesday, February 26, 2013

I don't know how they implemented Delay in TaskEx, perhaps it blocks the UI message pump and prevents dispatching back to the UI thread.

It is generally better to avoid that type of a fake-sync block though. You might be happier using a TaskCompletionSource to wait for the validation completed event.

ngm replied on Wednesday, February 27, 2013

Delay should be getting back on the captured context if one exists which is in this case probably DispatcherSynchronizationContext if it's WPF client.

Therefore, if UI thread is not blocked the completition should be invoked i.e. the rest of code executed.

You can try configuring awaiter not to capture context:

Await TaskEx.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);

So the completition is going to be invoked on worker thread.

However, I remember reporting issue with ConfigureAwait in earlier versions of Bcl.Async, so you can try clearing current context prior to awaiting:

SynchronizationContext.Current = null;

That being said, it's really not good to sync BO persistence with this timing sensitive loop.

- ngm

 

g.beraudo@castsoftware.com replied on Wednesday, February 27, 2013

Hi ngm,

I'm still using the old version of the AsyncTargettingPack (and not the latest Bcl.Async). I'm in a Winform context.. in case this has side effects.

I will try your proposal.

Gilles

ngm replied on Wednesday, February 27, 2013

Gilles,

Get far away from those old versions, they are very buggy and I can confirm there are a number of them related to the context capturing.

- ngm

 

g.beraudo@castsoftware.com replied on Saturday, March 02, 2013

Hi ngm,

Upgrading to the latest Bcl.Async prerelease (1.0.14) fixed the issue: the call is not blocking anymore.

I'm aware this is not the good solution, but I was pessimistic implemeting something clean if this was not working.

I'll be in vacation for the next 2 weeks (in between as a workaround, we moved our async rules to be sync). I will update the thread when I get back with a 'go live' solution.

Thanks for your help.

Gilles

ngm replied on Wednesday, February 27, 2013

Rocky,

Do people complain a lot about this?

What about async persistence method variant that awaits all async broken rules prior to invoking Data Portal? Or even dedicated broken rules method that when awaited will make sure all async rules are complete prior to completing itself. That Save might use this one internally.

I know that async rules are not awaiting friendly but it wouldn't be too complicated wrapping it since there's already central point where BusinessRules handle completitions from async rules.

Along that thinking, any plans to redesign rules engine to be fully async/await?

- ngm

 

RockfordLhotka replied on Thursday, February 28, 2013

ngm

Do people complain a lot about this?

The use of async/await is limited, as it is so new. As a result, this is (I think) the first thread where this has come up - at least since 2008 when Silverlight started rolling out - and of course the answer then is different from now.

To make this easier (originally for unit testing, and then for a Magenic project last year) there's an event raised to indicate when rule processing is complete. That makes it relatively easy to wait for all rules to finish before moving on by using a TaskCompletionSource.

If this becomes enough of a common issue I'm sure BusinessBase could have a method that returned a Task on which you could wait - probably just implementing the use of a TCS in that method :)

ngm replied on Friday, March 01, 2013

You probably think on AllRulesComplete method that raises ValidtionComplete event?

If so, that's probably okay to be used for helper or extension method that implements syncing Save which waits for async rules to complete. But I'm not quite sure it's good idea for CSLA internal implementation.

Having as critical method as Save synced by subscribing to external event where there's no guarantee of the invocation order can potentially lead to trouble.

Anyway, rules engine is really impressive piece of CSLA. It would be nice seeing its async mechanism implemented as awaitables at some point.

- ngm

 

g.beraudo@castsoftware.com replied on Tuesday, October 08, 2013

Hi Rocky,

I hope you are recovering well.

I'm reopening the discussion, as my attempts to solve the problem were not successful yet.

Following your advice, I'm trying to override the SaveAsync using a TaskCompletionSource to block until being notified that all async rules completed.

It is my first attempt to use TaskCompletionSource, so I might miss the obvious... I'm still using CSLA 4.5.10 / .NET 4 client profile, with the latest Async targetting packs.

I'm sharing my code below, in case there is something obvious that I'm missing.

Regads,

Gilles

 

 Protected Overrides Async Function SaveAsync(forceUpdate As Boolean, userState As Object, isSync As Boolean) As Task(Of T)

      If Me.IsBusy Then

        Dim busyTcs As New TaskCompletionSource(Of Boolean) 'Creates a blocking task until all async rules complete

        Dim saveTcs As New TaskCompletionSource(Of T) 

        Dim bw As New Csla.Threading.BackgroundWorker() 'Ensure that I do not wait for the busyTcs on the UI thread.

        AddHandler bw.DoWork, Sub(s, o)

                     busyTcs.Task.Wait(TimeSpan.FromSeconds(10))  'This always block for 10s!

                     o.Result = MyBase.SaveAsync(forceUpdate, userState, isSync).Result

                   End Sub

        AddHandler bw.RunWorkerCompleted, Sub(s, o)

                           If o.Error Is Nothing Then

                             saveTcs.TrySetResult(CType(o.Result, T))

                           Else

                             saveTcs.TrySetException(o.Error)

                           End If

                         End Sub

        AddHandler ValidationComplete, Sub(sender, e)

                          busyTcs.TrySetResult(True)

                        End Sub

        If Not Me.IsBusy Then busyTcs.TrySetResult(True)

 

        bw.RunWorkerAsync()

        Return Await saveTcs.Task

      Else

        Return Await MyBase.SaveAsync(forceUpdate, userState, isSync)

      End If

Copyright (c) Marimer LLC