Our application has been working fine until we upgraded to .NET 4.7. Now it appears that the OperationContext.Current is null after performing async/await calls.
The OperationContext.Current continues to be set properly on .NET 4.6.2 and lower, just not on machines that do not have .NET 4.7 installed.
I see in ServiceModelAppSettings that
DefaultDisableOperationContextAsyncFlow = true;
Is this this a breaking change in .NET 4.7?
BTW, my guess is that this is something developers would want to opt out of, not in.
I can confirm that setting the following in our app.config file, fixed the problem for us.
<appSettings>
<add key="wcf:disableOperationContextAsyncFlow" value="false" />
</appSettings>
Hi Jonreis,
The default value of the appSettings switch is correct and it is indeed a breaking change. I strongly encourage you to use the default behavior of 4.7 and work around the OperationContext.Current being null after an async/await call by doing something like this:
public async Task Test()
{
OperationContext currentContext = OperationContext.Current;
await Task.Delay(100); // this is the await call.
//use currentContext instead of OperationContext.Current
}
In .NET 4.6.2 we added support for WCF’s OperationContext.Current to correctly flow with the ExecutionContext in asynchronous continuations.
Previously, the internal implementation of OperationContext.Current was to store the Current context using a ThreadStatic variable, which used the thread's local storage (TLS) to store the data associated with Current context. The problem with this is if there was a change in the execution context of the method call (i.e. a thread change caused by awaiting another operation), any subsequent calls to OperationContext.Current would result in null being returned. The way we implemented the feature was to rely on AsyncLocal
There are currently are two known issues around this feature that break old scenarios.
Issue #1: Users might obtain a NULL result when calling OperationContext.Current
This issue occurs when one creates an OperationContextScope and proceeds to call OperationContext.Current within the using clause. A common pattern that is prone to this failure is the following:
using (new OperationContextScope(OperationContext.Current))
{
OperationContext context = OperationContext.Current; //OperationContext.Current returns null
// ...
}
To work around this issue, one needs to change the code to something similar to the following:
OperationContext ocx = OperationContext.Current;
using (new OperationContextScope(OperationContext.Current))
{
OperationContext.Current = new OperationContext(ocx.Channel);
// ...
}
Issue #2: Users might run into a deadlock when using Reentrant services.
This issue occurs when one creates a Reentrant service – which restricts instances of the service to one thread of execution at a time. Users prone to running into this problem will have the following ServiceBehaviorAttribute in their code:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant)]
To work around this issue, users should set the ConcurrencyMode of their service to ConcurrencyMode.Single or ConcurrencyMode.Multiple, and provide the appropriate synchronization primitives to ensure a single thread can execute a single method at a time.
We have rolled out an update of System.ServiceModel.dll and we strongly encourage users to patch their existing .NET 4.6.2 framework. The patch fixes Issue #1, disables the flow of OperationContext.Current by default, and provides an Opt-In option to enables feature.
To enable the flow of OperationContext.Current across multiple threads in async operations, use the following AppSettings key:
Setting the value of Switch.System.ServiceModel.DisableOperationContextAsyncFlow to true will disable the flow of the ExecutionContext in OperationContext.Current and will effectively revert the behavior to pre-4.6.2, which is the default behavior after this patch. Setting the value to false will enable the feature and should not be used in Reentrant services.
Thank you for the information jcondex.
We will just have to be careful with our customers that are currently running our product on .NET 4.6.2 because if they upgrade to 4.7 without first upgrading our product, it will fail with NREs due to this change.
We do not use ConcurrencyMode.Reentrant, so we should be good setting disableOperationContextAsyncFlow to false in the App.config.
I have and Asp.Net app running 4.7.2. I want to start using async processing.
All our end points have ConcurrencyMode set to Multiple (not Reenterant).
I am setting DisableOperationContextAsyncFlow in my appSettings in my web.config and I see the change in the System.ServiceModel.ServiceModelAppSettings.DisableOperationContextAsyncFlow is false. The problem is my OperationContext.isAsyncFlowEnabled is false. How is that set?
In case this helps others. We are running .NET 4.7 (hosted in Service Fabric) and using await Task.WhenAll()
For some reason setting the following does not work:
<add key="Switch.System.ServiceModel.DisableOperationContextAsyncFlow" value="false"/>
But setting this does work:
<add key="wcf:disableOperationContextAsyncFlow" value="false"/>
Not sure what the different is or why there are two. Would be great if someone from @MS can explain.
@Jaans did you manage to work out the difference between the two keys? Does the "wcf:" key replace the "Switch" key?
Has anyone (@jcondex ?) had issues with OperationContext.Current occasionally returning null when completing an async call on a different thread? 99% of the time it works as expected (i.e. as per the 4.6.2 improvement) but the other 1% of the time returns null.
I've been hitting a brick wall on this for a few days now and am thinking of resorting to stashing OperationContext.Current before making an async call and if it's null after the call completes (on a different thread), then explicitly re-assign it. But this kind of defeats the purpose of the 4.6.2 ExecutionContext changes though.
Hi @jcondex,
why exactly do you "strongly encourage to use the default behavior of 4.7 and work around the OperationContext.Current being null"? I didn't find any official documentation page where setting the wcf:disableOperationContextAsyncFlow to false is not recommended (except for the reentrant concurrency mode case). Considering we use the ConcurrencyMode.Multiple concurrency mode, does setting the wcf:disableOperationContextAsyncFlow to false have any negative implications or OperationContext.Current issues?
Hi Jonreis,
The default value of the appSettings switch is correct and it is indeed a breaking change. I strongly encourage you to use the default behavior of 4.7 and work around the OperationContext.Current being null after an async/await call by doing something like this:
public async Task Test()
{
OperationContext currentContext = OperationContext.Current;
await Task.Delay(100); // this is the await call.
//use currentContext instead of OperationContext.Current
}
It works
C#
OperationContext currentContext = OperationContext.Current;
await Task.Delay(100); // this is the await call.
//use currentContext instead of OperationContext.Current
Hi Jonreis,
The default value of the appSettings switch is correct and it is indeed a breaking change. I strongly encourage you to use the default behavior of 4.7 and work around the OperationContext.Current being null after an async/await call by doing something like this:
public async Task Test()
{
OperationContext currentContext = OperationContext.Current;
await Task.Delay(100); // this is the await call.
//use currentContext instead of OperationContext.Current
}In .NET 4.6.2 we added support for WCF’s OperationContext.Current to correctly flow with the ExecutionContext in asynchronous continuations.
We have an issue in a custom endpoint behaviour that depends on OperationContext.Current but it when we call our external service async. I have added the following two settings to the configuration file of your WCF services project but unfortunaly this does not help in our case:
<appSettings>
<add key="wcf:disableOperationContextAsyncFlow" value="false"/>
<add key="Switch.System.ServiceModel.DisableOperationContextAsyncFlow" value="false"/>
</appSettings>
The work-around here is:
var ctx = OperationContext.Current;
return await Task.Run(() =>
{
using (new OperationContextScope(ctx))
{
// call to external service here
}
});
Most helpful comment
Hi @jcondex,
why exactly do you "strongly encourage to use the default behavior of 4.7 and work around the
OperationContext.Currentbeing null"? I didn't find any official documentation page where setting thewcf:disableOperationContextAsyncFlowtofalseis not recommended (except for the reentrant concurrency mode case). Considering we use theConcurrencyMode.Multipleconcurrency mode, does setting thewcf:disableOperationContextAsyncFlowtofalsehave any negative implications orOperationContext.Currentissues?