你能用自己的话解释STA和MTA吗?
什么是公寓线程,它们只与COM有关吗?如果是这样,为什么?
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 …
因此,在此之后,我决定在专用的STA线程上显式实例化COM对象.实验表明COM对象需要一个消息泵,我通过调用它来创建Application.Run():
private MyComObj _myComObj;
// Called from Main():
Thread myStaThread = new Thread(() =>
{
_myComObj = new MyComObj();
_myComObj.SomethingHappenedEvent += OnSomthingHappened;
Application.Run();
});
myStaThread.SetApartmentState(ApartmentState.STA);
myStaThread.Start();
Run Code Online (Sandbox Code Playgroud)
如何从其他线程发布STA线程的消息泵消息?
注意: 为了简洁起见,我对问题进行了大量编辑.@Servy的答案的某些部分现在似乎无关紧要,但它们是针对原始问题的.
我正在使用来自第三方库的COM对象来生成定期事件.当我使用Winforms应用程序中的库,将对象作为类成员并在主窗体线程中创建它时,一切正常.但是,如果我从另一个线程创建对象,我不会收到任何事件.
我的猜测是我需要在用于创建对象的同一个线程中有某种事件循环.
我需要从控制台应用程序中使用此对象.我想我可以使用Application.DoEvents,但我宁愿不在控制台App中包含Winforms命名空间.
我怎么解决这个问题?
更新3(2011-06-15):供应商终于回答了.简而言之,他们说Application.Run创建的消息泵与Thread.Join创建的消息泵之间存在一些差异,但他们不知道这有什么区别.
我同意他们; 任何关于此事的光明都会非常感激.
更新:
从理查德评论到mdm回答:
如果其他组件是单线程并从MTA实例化,则Windows将创建工作线程+窗口+消息泵并执行必要的编组.
试着听从他的建议,我正在做以下事情:
更新2:
我改变了JoãoAngelo回答后的代码.
using System;
namespace ConsoleApplication2
{
class Program
{
[STAThread]
static void Main(string[] args)
{
MyComObjectWrapper wrapper = new MyComObjectWrapper();
}
}
class MyComObjectWrapper
{
MyComObject m_Object;
AutoResetEvent m_Event;
public MyComObjectWrapper()
{
m_Event = new System.Threading.AutoResetEvent(false);
System.Threading.Thread t = new System.Threading.Thread(() => CreateObject());
t.SetApartmentState (System.Threading.ApartmentState.STA);
t.Start();
Wait();
}
void ObjectEvt(/*...*/)
{
// ...
}
void Wait()
{
m_Event.WaitOne();
}
void CreateObject()
{
m_Object = new MyComObject();
m_Object.OnEvent …Run Code Online (Sandbox Code Playgroud) 我目前正在查看我们在.Net Framework 2.0 Windows服务(C#)中遇到的问题,该服务具有X个运行MTA线程以访问COM组件.每个线程初始化它自己的com组件对象的实例.com组件对象没有任何UI元素.它只是业务逻辑,它与sql server数据库和带有com接口的C#dll进行通信,而com接口又进行套接字通信并访问同一个sql server数据库.
通过我的研究,我发现你不应该在MTA线程上实例化STA COM组件,但我找不到任何特定的文本来说明这是什么危险或者可能是因为我不理解COM线程公寓那很好.
上面描述的模型会出现并发问题吗?即使每个MTA线程都在创建自己的STA COM对象?
我们实际遇到的问题是对象引用未设置为以下代码块中连接字符串的setter中的对象错误的实例.这发生在c ++ COM对象调用的C#COM对象中:
IDbConnection connection;
//Code omitted for brevity where connection is initialized
connection.ConnectionString = myConnectionString;
Run Code Online (Sandbox Code Playgroud)
异常类型:System.NullReferenceException消息:未将对象引用设置为对象的实例.数据:System.Collections.ListDictionaryInternal TargetSite:System.Data.OracleClient.OracleClient.OracleClient.OracleConnection.set_ConnectionString(String value)中System.Data.OracleClient.OracleConnection.ConnectionString_Set(String value)处的Void ConnectionString_Set(System.String)