此问题已由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成功使用了类似的单线程模型 …
TL; DR:运行任务中的死锁StaTaskScheduler.长版:
我使用的是StaTaskScheduler从ParallelExtensionsExtras中通过平行小组,举办由第三方提供的一些遗留STA COM对象.StaTaskScheduler实现细节的描述如下:
好消息是TPL的实现能够在MTA或STA线程上运行,并考虑到底层API的相关差异,如WaitHandle.WaitAll(当方法提供多个等待句柄时,它只支持MTA线程).
我认为这意味着TPL的阻塞部分将使用等待API来提供消息,例如CoWaitForMultipleHandles,以避免在STA线程上调用时出现死锁情况.
在我的情况下,我相信发生以下情况:进程内STA COM对象A调用进程外对象B,然后期望从B通过回调作为传出调用的一部分.
以简化形式:
var result = await Task.Factory.StartNew(() =>
{
// in-proc object A
var a = new A();
// out-of-proc object B
var b = new B();
// A calls B and B calls back A during the Method call
return a.Method(b);
}, CancellationToken.None, TaskCreationOptions.None, staTaskScheduler);
Run Code Online (Sandbox Code Playgroud)
问题是,a.Method(b)永远不会回来.据我所知,这是因为内部阻塞等待BlockingCollection<Task>不会引发消息,因此我对引用语句的假设可能是错误的.
EDITED相同的代码工作测试WinForms应用程序的UI线程上执行时(即,提供TaskScheduler.FromCurrentSynchronizationContext()的,而不是staTaskScheduler到Task.Factory.StartNew).
解决这个问题的正确方法是什么?我应该实现一个自定义同步上下文,它将显式地使用消息CoWaitForMultipleHandles …
我对 C# marshal 的 COM 对象是否在线程之间感到非常困惑。为此,我有一个以任务并行方式加载一组文件的应用程序。我正在使用StaTaskScehduler使用 COM 对象加载文件。加载 COM 对象后,我将该对象存储在中央列表中。
然后我稍后尝试对这些数据执行一些处理,再次使用 STATaskScheduler。但是在这一点上我遇到了一个问题。我收到如下异常:
An unhandled exception of type 'System.Runtime.InteropServices.InvalidComObjectException' occurred in MadCat.exe
Additional information: COM object that has been separated from its underlying RCW cannot be used
Run Code Online (Sandbox Code Playgroud)
现在我的理解是我收到此错误是因为该对象尚未编组到新线程中。我认为这是 C# 为您做的事情?
如何在一个线程中创建一个单元线程 COM 对象,然后从另一个线程使用它?
我在这里吠错树了吗?我什至不应该在我的线程中使用 Sta 公寓吗?我可以保证对象永远不会同时从多个线程访问。任何想法都非常感谢。
编辑:COM 对象定义如下:
[
coclass,
threading( apartment ),
vi_progid( [Namespace.Class] ),
progid( [Namespace.Class].6 ),
version( 6.0 ),
uuid( GUID_C[Class] ),
helpstring( [Class]" Class" )
]
Run Code Online (Sandbox Code Playgroud)
所以根据我的理解,这是一个公寓线程对象,对吗?我刚刚尝试使用未设置公寓状态的修改后的任务调度程序(默认情况下为 MTA?)。当我在一个线程中创建它并从另一个线程使用它时,这个对象似乎确实有效。这是安全的还是会以其他方式回来咬我?
COM 的线程模型总是让我很困惑:/