引发异常的异步方法不会将线程上下文恢复到同一线程
本文关键字:线程 上下文 恢复 一线 异常 异步方法 | 更新日期: 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。