Losing Csla Context on continuation when async/await returns

Losing Csla Context on continuation when async/await returns

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


Jaans posted on Thursday, March 12, 2015

I'm running into an issue where the continuation code (i.e. the code that is run when an "await" call returns) loses the CSLA contexts (ApplicationContext, GlobalContext, etc.).

Here's a pseudo example of what I'm doing (standard async/await CSLA Factory stuff). This is the calling code:

line 1: var businessObject = await BusinessLogic.MyType.GetAsync();
line 2: DoSomethingWith(businessObject); 

The MyType.GetAsync() factory implementation uses the async DataPortal, like this:

...
return await DataPortal.FetchAsync<MyType>(...);
... 

I have some values set in Csla.GlobalContext, and prior to line 1 executing, these values can be accessed. However, when the continuation returns on line 2, the Csla.GlobalContext would be empty.

Have I missed something regarding the use of CSLA with async/await when it comes to the CSLA Contexts  ? My expectation would have been that CSLA await continuations would implicitly re-apply the original context, or have I just missed something silly like a configuration option or something?

I'm using CSLA 4.5.601.

Thanks

JonnyBee replied on Monday, March 16, 2015

Hi Jaans, 

I would expect that you had the ApplicationContext available after you return from the async method. 

Do you return on the same thread ID? 

Which dataportal proxy do you use? 

My expexted behavior would be that the ApplicationContext would be transferred to the background tread but NOT updated when the call returns (as you could have multiple async operations going in parallell).

Jaans replied on Monday, March 16, 2015

Hei Jonny

This is implemented in a Windows Service project, and the continuation doesn't return on the same thread no.

The data portal proxy being used is the WcfPortal (Csla.Server.Hosts.IWcfPortal).

I've been fiddling with this, and found that if I use the Csla.Threading.CslaTaskScheduler to manually create the Tasks from, then the returning continuation does indeed have the CSLA contexts (ApplicationContext, GlobalContext, etc.) setup, even if returning on a different thread.

Unfortunately, it creates a lot of ugly plumbing code for each async based call, and it makes me wonder if CSLA could/should not be doing this implicitly.

Here's a example of it:

// We need to use the cslaTaskScheduler if we wish to retain the csla context on the continuation
// - Also, see this link about "await await" usage: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

var cslaTaskScheduler = new Csla.Threading.CslaTaskScheduler();
var uowResult = await await Task.Factory.StartNew( () => BusinessLogic.XXXX.YYYY.SomeObjectList.Get( id ), CancellationToken.None, TaskCreationOptions.DenyChildAttach, cslaTaskScheduler );

It works well enough, but it's butt-ugly and our juniors will likely trip over this. Not sure if I can create a pretty Extension method alternative for it somehow. 

PS: The double 'await await' is intentional - refer to the interesting link in the comments for more

Thanks,
Jaans 

JonnyBee replied on Tuesday, March 17, 2015

Hi,

That explains it. Csla.ApplicationContext is stored in a named slot on the current thread. So if the continuation is run on another thread then the context is lost. 

The issue here being that ConsoleApplications and Windows Services runs under the default SynchroizationContext only. 

See this articla for how to run with a custom SynchronizationContext:

http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx 

 

Jaans replied on Tuesday, March 17, 2015

Thanks Jonny - that article is great!

At first glance, it would be useful if CSLA had some built-in support for this, perhaps even some sort of SynchronisationContextProvider that hooks intro the DataPortal mechanism that applies a SynchronizationContext for the caller's continuation.

OTOH, if CSLA had some custom SynchronizationContext's available that would be helpful too, as I'm sure I'm not the only person needing this, and it would be needed for UnitTests too if one needed the Contexts restored after the continuation.

I'm thinking something like one or two custom SynchronizationContext's for non-UI / non-Web scenarios, the first being a RestoreCslaContextSynchronizationContext that can restore the CSLA Contexts for the continuation (even if on a separate thread to the initial invocation). And maybe a second one, something like a ContinueOnOriginalThreadSynchronizationContext that runs the continuation on the original thread.

Just thinking out load there, though a RestoreCslaContextSynchronizationContext would be super!

I'll work through the article and try to create something myself and see how I go with that.

Thanks for the guidance, very helpful indeed.

JonnyBee replied on Thursday, March 19, 2015

Hi Jaans,

Just some general caution.

There is an internal class Csla.Threading.ContextParams that could be made public.

This class holds the context values (except LocalContext) and can restore the values on another thread. (in CslaTaskSchduler to set the ApplicationContext and User on the async thread)

You could also consider to use:
<businssBase>.SaveAsync(false, contextParams, false)
(or create another wrapper) so that you can pass the contextParamers as a userState (pass-through) and restore values in OnSaved event.  

You could also consider to hook into the DataPortal.DataPortalInvokeComplete to set the ApplicationContext.ClientContext and GlobalContext when a dataportal call is completed. You have access to the DataPortalContext object in this event and the DPC contains both User, calling threads cultures and ClientContext + GlobalContext (altho the ClientContext and GlobalContext is internal - so you must use reflection).

I believe there are 2 issues with possible solutions here: 

  1. Make sure to return from async call on the same caller thread where the ApplicationContext still exists.
  2. Set the context values on DataPortalInvokeCompleted
  3. pass contextParams as a pass-through object and restore values in OnSaved event

1) may be resolved with a custom synchronization context (non CSLA specific)
2) or  3) may be resolved with using Reflection or make some changes to CSLA codebase.

If you choose to use Reflection have a look at Fasterflect - It has helped me numerous times.

Copyright (c) Marimer LLC