【发布时间】:2018-02-12 18:39:00
【问题描述】:
我有一个 Action 可以发送到三个 Cosmos DB 存储库以检索信息。动作代码如下:
var task1 = _itemRepository.RetrieveByIdAsync(id);
var task2 = _partsRepository.RetrieveForItemAsync(id);
var task3 = _purchasesRepository.RetrieveForItemAsync(id, currentUserId);
var userPurchased = await _userPurchaseRepository.HasUserPurchased(Session.UserId) // this is awaited because it is a SQL task and can only execute one at a time
await Task.WhenAll(task1, task2, task3);
我在调试时注意到的是任务似乎在不同的线程上运行。 System.Threading.Thread.CurrentThread.ManagedThreadId 与任务运行前、每个任务以及等待任务后不同。
进入前的线程 ID - 5
项目存储库线程 ID - 61
部件存储库线程 ID - 5
购买存储库线程 ID - 60
WaitAll - 6 后的线程 ID
存储库方法最终都会调用一个通用的 QueryAsync 方法,如下所示:
public async Task<IEnumerable<TDomainObject>> QueryAsync(Expression<Func<TDomainObject, bool>> predicate, FeedOptions options)
{
var query = Client.CreateDocumentQuery<TDomainObject>(Collection.DocumentsLink, options).Where(predicate).AsDocumentQuery();
List<TDomainObject> results = new List<TDomainObject>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<TDomainObject>());
}
return results;
}
我的理解是,所有异步/等待处理都应该使用同一个请求线程,我担心我的代码可能无法达到应有的性能。
我没有将线程 ID 用于任何事情,我只是想确保我没有创建不必要的线程。
编辑:最初让我对此进行调查的是,我发现 QueryAsync 方法中的调用堆栈,当断点结果时,调用堆栈开始于存储库而不是在控制器中。
【问题讨论】:
-
ASP.NET 调度程序没有线程关联。继续处理完成通知的任何线程池线程是一件好事,它可以节省上下文切换。
-
只有当你使用线程 ID 做某事时,这才是一个问题。首先,Wich 是个坏主意。如果您需要识别任务,则应通过在启动期间为其提供/检索的变量来完成。像一个简单的运行计数器(不要忘记检索/更新期间的竞争条件保护)。
-
ASP.NET Core 使用/管理线程池。当您启动一个请求时,它会在一个线程上运行。当你等待一个异步 I/O 任务时,原来的线程会返回到线程池中,并且可以用于其他请求,直到操作完成,然后它会从线程池中选择另一个空闲/可用的线程继续执行。它可能与原始线程相同,也可能不同(因为原始线程可能已被另一个请求或延续使用)。除非您调用
Task.Run或TaskFactory.StartNew(在 99% 的情况下在 Web 应用程序中不好),否则不会产生新线程 -
@Tseng 不建议显式使用
ConfigureAwait(false),否则继续会请求同一个线程并可能导致延迟 -
@MrinalKamboj:是的,这是编写可重用库的推荐方法。在 ASP.NET Core 中不再需要,因为不再有 AspNetSynchronizationContext。事实上,ASP.NET Core 放弃了在 ASP.NET Core 代码中使用
.ConfigureAwait(false)。但在通用库(可用于桌面、UI、WPF 或移动应用程序)中,仍然建议在每个等待的任务上调用.ConigureAwait(false)。但是在纯 ASP.NET Core 库中(依赖于Microsoft.AspNetCore.*库的库不再需要了
标签: c# multithreading asp.net-core async-await