抛出异常的异步方法不会将线程上下文恢复回同一个线程

时间:2016-02-17 15:35:55

标签: c# asynchronous dependency-injection async-await

当我使用异步等待并抛出异常时,线程上下文正在丢失。在我的代码中,我使用注册来解析每个线程的依赖注入,所以我需要在同一个线程上执行我的代码。

这就是它的设置方式:

我有一个方法会尝试使用异步调用不同的通信器,当一个抛出异常它将进入下一个异常时:

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
            }
        }
    }
}

下面是一个单元测试,它包含一个始终抛出异常的通信器,以及一个尝试获取注册到原始线程的依赖项的单元测试。

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);
}

当在单元测试中解析依赖关系时,它们不相等。在查看之后,我看到CurrentThread.ManagedThreadId的值在抛出异常时发生了变化。然后,当它在VistRequestAsync方法中被捕获时,CurrentThread.ManagedThreadId永远不会恢复到其原始状态。因此,依赖注入无法获得相同的实例,因为它现在在不同的线程上运行。

最初,我正在使用.ConfigureAwait(false)和await。然后我尝试将其设置为true,然后我开始看到它有时会返回相同的线程。这听起来很像this answer中所说的。

post about the synchronization context and async听起来很像我面临的问题。我的麻烦是我正在使用WebApi并在事情发生时需要回复,因此我不确定如何使用他的消息泵并异步等待答案。

1 个答案:

答案 0 :(得分:1)

Async使用ThreadPool处理任务。这意味着无法保证异步操作将在同一个线程上启动和完成。

首次等待异步任务时,任务将放在工作队列中。任务调度程序尽快从队列中获取该任务并将其分配给许多可用线程之一。

有关详细信息,请参阅TPL结构概述:https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx

如果您需要与线程一起流动的上下文,请查看使用类似logical call context或CallContext。LogicalSetData / LogicalGetData。

的内容。

但您所看到的行为是正确的,并且如上所述与是否抛出异常无关。您将在异步任务的调度,执行和完成的各个点看到不同的线程ID。