1

When I am using async await and an exception is thrown the thread context is being lost. In my code I'm using dependency injection that registered to resolve per thread so I need to execute my code on the same thread.

This is how it is setup:

I have a method that will try calling different communicators using async when one throws an exception it will go onto the next one:

public async Task<TResponse> VisitRequestAsync(Context context)
{
    /* ....
    prepare request from context
    .... */
    var communicatorEnumerableInstance = _communicatorService.GetCommunicatorInstanceEnumerable();
    foreach (var communicator in communicatorEnumerableInstance)
    {
        using (communicator)
        {
            var communicatorInstance = communicator as ICommunicator<TResponse, TRequest>;
            try
            {
                return await communicatorInstance.ProcessAsync(request).ConfigureAwait(true);
                break;// call will break out of the for-each loop if successful processed.
            }
            catch (Exception exception)
            {
                continue;// Continue to load next communication method/instance
            }
        }
    }
}

Below is a unit test that contains a communicator that always throws an exception and one that tries to get a dependency that is registered onto the original thread.

public class TestDependancy : ITestDependancy
{

}

public interface ITestDependancy
{ }

public class TestCommunicatorThrowsException :
    ICommunicator<ResponseType, RequestType>
{
    public async Task<ResponseType> ProcessAsync(RequestType request)
    {
        var task = Task.Run(() =>
        {
            throw new Exception();
            return new ResponseType();
        });
        return await task;
    }

    public void Dispose()
    {
    }
}

public class TestCommunicatorGetsDependency :
    ICommunicator<ResponseType, RequestType>
{
    public TestCommunicatorGetsDependency()
    { }
    public async Task<ResponseType> ProcessAsync(RequestType request)
    {
        TestDependancy = DefaultFactory.Default.Resolve<ITestDependancy>();
        var task = Task.Run(() => new ResponseType());
        return await task;
    }

    public ITestDependancy TestDependancy { get; set; }

    public void Dispose()
    {
    }
}

[TestMethod]
[TestCategory("Unit")]
public async Task it_should_be_able_to_resolve_interface_from_original_thread()
{
    var secondCommunicator = new TestCommunicatorGetsDependency();
    _communicators = new ICommunicator<ResponseType, RequestType>[]
        {new TestCommunicatorThrowsException(), secondCommunicator};
    _communicatorServiceMock.Setup(
        x => x.GetCommunicatorInstanceEnumerable(It.IsAny<string>(), It.IsAny<string>()))
        .Returns(_communicators);

    ((IFactoryRegistrar) DefaultFactory.Default).RegisterPerThread<ITestDependancy, TestDependancy>();
    var firstInstance = DefaultFactory.Default.Resolve<ITestDependancy>();

    await it.VisitRequestAsync(_context).ConfigureAwait(true);
    var secondInstance = secondCommunicator.TestDependancy;
    Assert.AreEqual(firstInstance, secondInstance);
}

When the dependencies are resolved in the unit test they are not equal. After looking into it I see that the value for CurrentThread.ManagedThreadId changes at the point when the exception gets thrown. Then when it is caught in the VistRequestAsync method the CurrentThread.ManagedThreadId is never restored to its original state. So then the dependency injection is unable to get the same instance because it is now operating on a different thread.

Originally, I was using .ConfigureAwait(false) with the await. Then I tried setting it to true and I started seeing it sometimes get the same thread back. Which sounds a lot like what is said in this answer.

This post about the synchronization context and async sounds a lot like the problem I am facing. My trouble is I'm using WebApi and need a response back when things get done so I'm not sure how to use his message pump and asynchronously wait for an answer.

Community
  • 1
  • 1
Simon The Cat
  • 604
  • 1
  • 8
  • 22
  • 3
    Well in your test, what synchronization context are you in? If you're not in one (e.g. it's running on the thread pool) then it's entirely reasonable for it to come back to a different thread. That has nothing to do with exceptions, and is just normal behaviour for async. – Jon Skeet Feb 17 '16 at 15:42
  • 2
    Side note - There's no point in calling `break` after `return await`, that line of code will never be reached. Also, calling `ConfigureAwait(true)` is redundant, as it is the default. – Yuval Itzchakov Feb 17 '16 at 15:45
  • How would I set the synchronization context for the test? I've tried doing this, but it didn't seem to make a difference: var context = new SynchronizationContext(); SynchronizationContext.SetSynchronizationContext(context); – Simon The Cat Feb 17 '16 at 15:46

1 Answers1

1

Async uses the ThreadPool to process tasks. This means that there is no guarantee that an async operation will start and complete on the same thread.

When a async task is first awaited, the task is put on a work queue. As soon as possible, the task scheduler grabs that task from the queue and assigns it to one of the many available threads.

For more information, see this overview of the structure of the TPL: https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx.

If you need a context that flows with the thread, look at using something like the logical call context or CallContext.LogicalSetData / LogicalGetData.

But the behavior you're seeing is correct, and as mentioned has nothing to do with whether or not an exception is thrown. You'll see different thread ids at various points of an asynchronous task's scheduling, execution, and completion.

Ayo I
  • 7,722
  • 5
  • 30
  • 40