这是一个冗长的问题!您将在开始时找到有关该问题的一些背景知识,然后是代码示例,这些代码示例已简化了表示形式,之后又简化了问题。请按您认为适合自己的任何顺序阅读!
我正在为与STA COM通信的应用程序编写概念证明部分。应用程序的这一部分需要在单线程单元(STA)上下文中运行,以便与所述STA COM通信。该应用程序的其余部分在MTA上下文中运行。
到目前为止,我想出的是创建一个 Communication 类,该类包含一个在STA中运行的while
循环。需要中继到COM对象的工作通过ConcurrentQueue
从外部排队到 Communication 类。然后在while循环中将工作项出队并执行工作。
这是一个static
类,包含一个旨在在STA状态下运行的循环,并检查COM是否需要完成某些工作并将其分派到处理程序。
static class Communication
{
#region Public Events
/// This event is raised when the COM object has been initialized
public static event EventHandler OnCOMInitialized;
#endregion Public Events
#region Private Members
/// Stores a reference to the COM object
private static COMType s_comObject;
/// Used to queue work that needs to be done by the COM object
private static ConcurrentQueue<WorkUnit> s_workQueue;
#endregion Private Members
#region Private Methods
/// Initializes the COM object
private static void InternalInitializeCOM()
{
s_comObject = new COMType();
if (s_comObject.Init())
{
OnCOMInitialized?.Invoke(null, EventArgs.Empty);
}
}
/// Dispatches the work unit to the correct handler
private static void HandleWork(WorkUnit work)
{
switch (work.Command)
{
case WorkCommand.Initialize:
InternalInitializeCOM();
break;
default:
break;
}
}
#endregion Private Methods
#region Public Methods
/// Starts the processing loop
public static void StartCommunication()
{
s_workQueue = new ConcurrentQueue<WorkUnit>();
while (true)
{
if (s_workQueue.TryDequeue(out var workUnit))
{
HandleWork(workUnit);
}
// [Place for a delaying logic]
}
}
/// Wraps the work unit creation for the task of Initializing the COM
public static void InitializeCOM()
{
var workUnit = new WorkUnit(
command: WorkCommand.Initialize,
arguments: null
);
s_workQueue.Enqueue(workUnit);
}
#endregion Public Methods
}
此类描述了需要完成的工作以及可能提供的任何参数。
enum WorkCommand
{
Initialize
}
此枚举定义了COM可以执行的各种任务。
class WorkUnit
{
#region Public Properties
public WorkCommand Command { get; private set; }
public object[] Arguments { get; private set; }
#endregion Public Properties
#region Constructor
public WorkUnit(WorkCommand command, object[] arguments)
{
Command = command;
Arguments = arguments == null
? new object[0]
: arguments;
}
#endregion Constructor
}
这是通过COM拥有Communication
的类的一个示例,它们是拥有或产生的,并且是对Communication
的抽象,以供使用在应用程序的其余部分。
class COMController
{
#region Public Events
/// This event is raised when the COM object has been initialized
public event EventHandler OnInitialize;
#endregion Public Events
#region Constructor
/// Creates a new COMController instance and starts the communication
public COMController()
{
var communicationThread = new Thread(() =>
{
Communication.StartCommunication();
});
communicationThread.SetApartmentState(ApartmentState.STA);
communicationThread.Start();
Communication.OnCOMInitialized += HandleCOMInitialized;
}
#endregion Constructor
#region Private Methods
/// Handles the initialized event raised from the Communication
private void HandleCOMInitialized()
{
OnInitialize?.Invoke(this, EventArgs.Emtpy);
}
#endregion Private Methods
#region Public Methods
/// Requests that the COM object be initialized
public void Initialize()
{
Communication.InitializeCOM();
}
#endregion Public Methods
}
现在,来看一下Communication.StartCommunication()
方法,更具体地说,这部分:
...
// [Place for a delaying logic]
...
如果此行被以下内容代替:
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
// OR
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(true);
在检查过程中的最后一站-Communication.InternalInitializeCOM()
线程的位置似乎是 MTA 。
但是,如果延迟逻辑更改为
Thread.Sleep(100);
CommunicationInternalInitializeCOM()
方法似乎在 STA 状态下执行。
检查是由Thread.CurrentThread.GetApartmentState()
完成的。
谁能向我解释为什么Task.Delay
会破坏STA状态?还是我在做其他错误的事情?
感谢您抽出宝贵的时间阅读问题!祝你有美好的一天!
答案 0 :(得分:3)
汉斯钉牢了它。从技术上讲,您的代码正在中断,因为没有SynchronizationContext
captured by the await
。但是,即使您写一个,也不够。
此方法的一个大问题是您的STA线程未启动。 STA线程必须抽取Win32消息队列,否则它们不是STA线程。 SetApartmentState(ApartmentState.STA)
只是告诉运行时这是一个STA线程;它不会将设为STA线程。您必须将其作为STA线程泵送消息。
您可以自己编写该消息泵,尽管我不知道有人敢于这样做。大多数人从WinForms (a la Hans' answer)或WPF安装消息泵。也可以使用UWP message pump来做到这一点。
使用提供的消息泵的一个很好的副作用是它们还提供了SynchronizationContext
(例如WinFormsSynchronizationContext
/ DispatcherSynchronizationContext
),因此await
可以正常工作。另外,由于每个.NET UI框架都定义了一个“运行此委托” Win32消息,因此基础Win32消息队列也可以包含您要排队到线程的所有工作,因此不再需要显式队列及其“运行程序”代码
答案 1 :(得分:0)
由于在Date
语句之后,您的代码在ThreadPool线程之一中运行,并且由于ThreadPool线程在设计上是MTA。
await Task.Delay()