Question

I have a method that is ran in the main application thread, but creates new Task for long-running operation and awaits it's result.

public async Task<CalculatorOutputModel> CalculateXml(CalculatorInputModel input)
{
    // 1
    return await Task.Factory.StartNew(
        new Func<CalculatorOutputModel> (() => 
        {
            // 2
            using (var ms = Serializer.Serialize(input))
            {
                ms.Position = 0;
                using (var rawResult = Channel.RawGetFrbXmlOutputs(ms)) // 3
                {
                    var result = Parser.Parse(rawResult);
                    return result;
                }
            }
        }),
        CancellationToken.None,
        TaskCreationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());
}

My problem is that the AppContext in points (1) and (2) are "a bit" different. In point 1 there is usual application context in Current property, that has all the data I need. In point 2, as you understand, there is null reference in AppContext.Current, so I can't access any data. The problem with accessing context in point 2 seems to be easy, just "catch" current context in local variable or pass it as the parameter. The problem for me is more difficult because I need to access the context somewhere in the deep of line marked as "3".
The class itself is derived from System.ServiceModel.ClientBase<TChannel> and the place where I need to get access to the context is the class that implements IClientMessageInspector.

class CalculatorMessageInspector : IClientMessageInspector
{
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if (AppContext.Current != null)
        {
            // never goes here
        }
    }
}

Just to clarify, here is the call stack (I can't pass my context inside required method): Call stack

So:

  1. I can't pass context to Channel's method because it does not make any sense;
  2. I can't save required parameters from context in Channel because it is proxy class;
  3. I can't save required parameters in CalculatorMessageInspector because it is created within the place where current context is already null.

Can anyone advise any method how can I stay within the same context in another thread? Or, at least, how can I pass parameter from the place marked "2" inside the methods hierarchy? Maybe, I can use SynchronizationContext somehow to achieve it? Thanks a lot for any suggestions.

Update

AppContext.Current consider to be the same as HttpContext.Current != null ? HttpContext.Current.Items[AppContextKey] : null

Update 2 So it seems, that in this case there is no common solution to persist the context. Hence, the only applicable solution in this concrete situation (that is quite specific) is to capture required parameters using closure and save then in an object, that will be available in required method (for me worked adding properties to the implementator of IEndpointBehavior, but that solution is a bit odd). Thereby, the most applicable solution is to throw away the asynchronous wrapping over the sync call, so the AppContext never "goes away". Mark Stephen's answer as the right then.

Was it helpful?

Solution

To use async and await on ASP.NET, you must target .NET 4.5 and turn off quirks mode. The best way to do this is to set /configuration/system.web/httpRuntime@targetFramework="4.5" in your web.config.

Also, you shouldn't use Task.Run or Task.Factory.StartNew on ASP.NET. So your code should look more like this:

public CalculatorOutputModel CalculateXml(CalculatorInputModel input)
{
    using (var ms = Serializer.Serialize(input))
    {
        ms.Position = 0;
        using (var rawResult = Channel.RawGetFrbXmlOutputs(ms))
        {
            var result = Parser.Parse(rawResult);
            return result;
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top