引发异常的异步方法不会将线程上下文恢复到同一线程

本文关键字:线程 上下文 恢复 一线 异常 异步方法 | 更新日期: 2023-09-27 18:34:57

当我使用异步等待并引发异常时,线程上下文正在丢失。在我的代码中,我使用注册为每个线程解析的依赖项注入,因此我需要在同一线程上执行我的代码。

这是它的设置方式:

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

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( with the await.然后我尝试将其设置为 true,我开始看到它有时会返回相同的线程。这听起来很像这个答案中所说的。

这篇关于同步上下文和异步的文章听起来很像我面临的问题。我的麻烦是我正在使用WebApi,并且在事情完成后需要回复,所以我不确定如何使用他的消息泵并异步等待答案。

引发异常的异步方法不会将线程上下文恢复到同一线程

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

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

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

如果您需要随线程流动的上下文,请考虑使用逻辑调用上下文或 CallContext.LogicalSetData/LogicalGetData 之类的内容。

但是您看到的行为是正确的,如前所述,与是否引发异常无关。您将在异步任务的计划、执行和完成的不同点看到不同的线程 ID。