38

I'm calling a WCF service from a WinRT app. The service requires that some headers are set for the authentication. The problem is that if I do multiple calls to the service simultaneously, I get the following exception:

This OperationContextScope is being disposed out of order.

The current code looks like the following:

public async Task<Result> CallServerAsync()
{
    var address = new EndpointAddress(url);
    var client = new AdminServiceClient(endpointConfig, address);

    using (new OperationContextScope(client.InnerChannel))
    {
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();

        var request = new MyRequest(...); 
        {
            context = context,
        };

        var result = await client.GetDataFromServerAsync(request);
    }
}

I found the following comment from the docs:

Do not use the asynchronous “await” pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call “await” for an async call, use it outside of the OperationContextScope block.

So it seems I'm clearly calling the service incorrectly. But what is the correct way?

Mikael Koskinen
  • 12,306
  • 5
  • 48
  • 63

3 Answers3

27

According to Microsoft documentation:

Do not use the asynchronous "await" pattern within a OperationContextScope block. When the continuation occurs, it may run on a different thread and OperationContextScope is thread specific. If you need to call "await" for an async call, use it outside of the OperationContextScope block.

So the simplest proper solution is:

Task<ResponseType> task;
using (new OperationContextScope(client.InnerChannel))
{
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();

    var request = new MyRequest(...); 
    {
        context = context,
    };

    task = client.GetDataFromServerAsync(request);
}

var result = await task;
zolty13
  • 1,943
  • 3
  • 17
  • 34
11

This is a known "issue" and for anyone stuck with this, you can simply run your call synchronously. Use GetAwaiter().GetResult(); instead since it doesn't schedule a Task at all, it simply blocks the calling thread until the task is completed.

public Result CallServer()
{
    var address = new EndpointAddress(url);
    var client = new AdminServiceClient(endpointConfig, address);

    using (new OperationContextScope(client.InnerChannel))
    {
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();

        var request = new MyRequest(...); 
        {
            context = context,
        };

        return client.GetDataFromServerAsync(request).GetAwaiter().GetResult();
    }
}
Danijel
  • 1,145
  • 8
  • 15
9

Everything seems to work quite well with the following code:

public async void TestMethod()
{
    var result = await CallServerAsync();
}

public Task<Result> CallServerAsync()
{
    var address = new EndpointAddress(url);
    var client = new AdminServiceClient(endpointConfig, address);

    using (new OperationContextScope(client.InnerChannel))
    {
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = GetHeader();

        var request = new MyRequest(...); 
        {
            context = context,
        };

        return client.GetDataFromServerAsync(request);
    }
}
Mikael Koskinen
  • 12,306
  • 5
  • 48
  • 63
  • 8
    This might work, but it will work by coincidence. Because you don't await the GetDataFromServerAsync call the thread-switch won't occur. The operation's context-scope is already disposed before the call has actually completed. The reason it works is probably because the outgoing header is added before the inner call awaits internally. – Ramon de Klein Dec 27 '16 at 10:20
  • 3
    You can await the task GetDataFromServerAsync returns right after the end of the using block (assign the task to a variable). – sich Mar 24 '17 at 15:22
  • @sich but in that case the client does not use the custom message properties. – supertopi Apr 16 '19 at 10:28
  • @supertopi why not, you initiate the operation in the using block, where all configuration is captured, and then only wait for completion outside of it. – sich Apr 16 '19 at 11:20