c# - 为什么 Task.Delay 会破坏线程的 STA 状态?

标签 c# multithreading com sta mta

简介

这是一个冗长的问题!您会在开头找到有关该问题的一些背景知识,然后是代码示例,这些示例已针对表示进行了简化,之后是问题。请按照您认为适合的顺序阅读!

背景资料

我正在为与 STA COM 通信的应用程序编写概念验证部分。这部分应用程序需要在单线程单元 (STA) 上下文中运行,以便与所述 STA COM 通信。应用程序的其余部分在 MTA 上下文中运行。

当前状态

到目前为止,我的想法是创建一个 Communication 类,其中包含一个 while 循环,在 STA 中运行。需要中继到 COM 对象的工作通过 ConcurrentQueue 从外部排队到 Communication 类。然后,工作项在 while 循环中出列并执行工作。

代码上下文

交流类

这是一个static 类,包含一个旨在在 STA 状态下运行并检查是否需要由 COM 完成某些工作并将工作分派(dispatch)给处理程序的循环。

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 状态?还是我在这里做错了什么?

谢谢!

感谢您抽出宝贵时间阅读问题!祝你有美好的一天!

最佳答案

汉斯做到了。从技术上讲,您的代码被破坏是因为没有 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 框架都定义了一个“运行此委托(delegate)”的 Win32 消息,因此底层 Win32 消息队列也可以包含您想要排队到您的线程的所有工作,因此显式队列及其“运行程序”代码不再是必要的。

关于c# - 为什么 Task.Delay 会破坏线程的 STA 状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55761773/

相关文章:

c# - 为什么应用程序无一异常(exception)地默默死去?

Java - 如何在等待数据的进程上实现线程

c# - C#中COM接口(interface)的显式接口(interface)实现

c# - C++ 从 C# COM DLL 调用函数

c# - 里面的按钮是 listView 在 UWP 的 View 中不显示

c# - 混叠问题

c# - 远程验证显示红框但没有错误信息? ASP-MVC 5

c++ - 在 OpenMP for 循环中调用函数

java - 多个线程读取变量

c++ - ATL COM 类的单个实例