此问题已由EF数据上下文 - 异步/等待和多线程触发.我已经回答了那个,但没有提供任何最终的解决方案.
最初的问题是,有许多有用的.NET API(如Microsoft Entity Framework DbContext),它们提供了设计用于的异步方法await,但它们被记录为不是线程安全的.这使它们非常适合在桌面UI应用程序中使用,但不适用于服务器端应用程序.[编辑] 这可能实际上并不适用DbContext,这是微软关于EF6线程安全的声明,自己判断. [/ EDITED]
还有一些已建立的代码模式属于同一类别,例如调用WCF服务代理OperationContextScope(在此处和此处询问),例如:
using (var docClient = CreateDocumentServiceClient())
using (new OperationContextScope(docClient.InnerChannel))
{
return await docClient.GetDocumentAsync(docId);
}
Run Code Online (Sandbox Code Playgroud)
这可能会失败,因为OperationContextScope在其实现中使用线程本地存储.
问题的根源AspNetSynchronizationContext是在异步ASP.NET页面中使用,以便从ASP.NET线程池中使用更少的线程来满足更多HTTP请求.使用时AspNetSynchronizationContext,await可以在与启动异步操作的线程不同的线程上对延续进行排队,同时将原始线程释放到池中,并可用于提供另一个HTTP请求.这大大提高了服务器端代码的可扩展性.该机制在It's All About the SynchronizationContext中有详细描述,必须阅读.因此,虽然没有涉及并发API访问,但潜在的线程切换仍然阻止我们使用上述API.
我一直在考虑如何在不牺牲可扩展性的情况下解决这个问题.显然,恢复这些API的唯一方法是维护可能受线程切换影响的异步调用范围的线程关联.
假设我们有这样的线程亲和力.这些调用中的大多数都是IO绑定的(没有线程).当异步任务处于挂起状态时,它所源自的线程可用于提供另一个类似任务的延续,该结果已经可用.因此,它不应该过多地损害可伸缩性.这种方法并不新鲜,事实上,Node.js成功使用了类似的单线程模型 …
等待线程安全吗?似乎Task类是线程安全的,所以我猜等待它也是线程安全的,但我还没有在任何地方找到确认.线程安全也是定制awaiter的要求 - 我的意思是IsCompleted,GetAwaiter等方法?即如果这些方法不是线程安全的,那么等待是否是线程安全的?但是,我不希望很快就需要定制的等待者.
用户场景的一个示例:假设我有一个后台任务,它以异步方式返回结果,然后从多个线程使用:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace scratch1
{
class Foo
{
Task<int> _task;
public Foo()
{
_task = Task.Run(async () => { await Task.Delay(5000); return 5; });
}
// Called from multiple threads
public async Task<int> Bar(int i)
{
return (await _task) + i;
}
}
class Program
{
public static void Main(string[] args)
{
Foo foo = new Foo();
List<Task> tasks = new List<Task>();
foreach (var i in Enumerable.Range(0, 100))
{
tasks.Add(Task.Run(async …Run Code Online (Sandbox Code Playgroud)