Wcf: Improve async handling of OperationContextScope

Created on 23 Jan 2018  路  5Comments  路  Source: dotnet/wcf

If you have the following code an exception will be thrown if the continuation happens on a different thread.

using(new OperationContextScope((IContextChannel)channel))
{
    await Task.Yield();
}

This is because OperationContextScope.Dispose() makes sure that the scope stored in an internal ThreadLocal matches the instance that is being disposed to prevent trying to dispose an instance that the current executing code doesn't own. This fails when using async/await. We need to improve this situation by at least not having it throw, and preferably have it flow the context through an async/await. We had some problems with this on the full framework due to interactions with re-entrant services. We shouldn't have the same problems on .NET Core as we don't support services, and we don't support the ConcurrencyMode property on CallbackBehaviorAttribute which would enable setting the concurrency mode to Reentrant.

bug feature request priority 2

Most helpful comment

Triage discussion: we should come up a solution that can work on both .NET Core and .NET Framework. For example, @mconnew proposed the idea of introducing a new AsyncOperationContextScope.

All 5 comments

This issue was surfaced via customer issue #2475

Triage discussion: we should come up a solution that can work on both .NET Core and .NET Framework. For example, @mconnew proposed the idea of introducing a new AsyncOperationContextScope.

While the approach below won't work with YieldAwaitable, however for more common situation I found solution by Andrew聽Nosenko works fine.

Failing case (example of async client method):

public async Task<SomethingResponse> GetSomethingAsync(GetSomethingRequest request)
{
    var channel = base.Channel;
    using (new OperationContextScope((IContextChannel)channel))
    {
        return await Task.Run(async () => (SomethingResponse)null);
    }
}

Fix:

public async Task<SomethingResponse> GetSomethingAsync(GetSomethingRequest request)
{
    var channel = base.Channel;
    using (var scope = new FlowingOperationContextScope((IContextChannel)channel))
    {
        return await Task.Run(async () => (SomethingResponse)null).ContinueOnScope(scope);
    }
}

@mconnew just saw this and it may be of interest to an issue I ran into recently using async calls. You mention in the other issue

We made a change in the full framework to move OperationContext/OperationContextScope to be propagated through async and it broke some people who were using some unusual coding patterns on the service side so we had to do a partial revert and make it switchable through app settings

Can you provide a link to any sort of documentation about this switch or a description of what the app setting is to allow OperationContextScope to flow in client side async calls on Framework?

I just discovered the documentation for this is pretty hard to parse as it's buried deep in a migration document. The easiest place to get info is actually this github issue: https://github.com/Microsoft/dotnet/issues/403

Was this page helpful?
0 / 5 - 0 ratings