为什么Task.Delay打破了线程的STA状态?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么Task.Delay打破了线程的STA状态?相关的知识,希望对你有一定的参考价值。

Introduction

这是一个冗长的问题!您将在开始时找到有关该问题的一些背景知识,然后是代码示例,这些代码示例已经过简化以表示,之后是问题。请阅读您认为对您有益的任何订单!

Background information

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

Current state

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

Code context

沟通课

这是一个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
}

The problem

现在,看看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()完成。

The Question

任何人都可以向我解释为什么Task.Delay打破了STA状态?或者我在做其他错误的事情吗?

Thank you!

感谢您花时间阅读这个问题!祝你有美好的一天!

答案

汉斯已经钉了它。从技术上讲,你的代码是破碎的,因为没有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消息队列还可以包含您要排队到线程的所有工作,因此显式队列及其“运行程序”代码不再必要。

另一答案

因为在await Task.Delay()语句之后,你的代码在一个ThreadPool线程中运行,并且因为ThreadPool线程是MTA设计的。

var th = new Thread(async () =>
        {
            var beforAwait = Thread.CurrentThread.GetApartmentState(); // ==> STA 

             await Task.Delay(1000);

            var afterAwait = Thread.CurrentThread.GetApartmentState(); // ==> MTA

        });

        th.SetApartmentState(ApartmentState.STA);
        th.Start();

以上是关于为什么Task.Delay打破了线程的STA状态?的主要内容,如果未能解决你的问题,请参考以下文章

Thread.Sleep x Task.Delay [重复]

为啥 scheduleAtFixedRate(task, delay, period) 不能按计划工作?

C#中的Task.Delay()和Thread.Sleep()区别

Thread.Sleep(2500) 与 Task.Delay(2500).Wait()

Windows 服务卡在“启动”状态以及 Thread.Sleep() 和 Task.Delay() 之间的区别

Task.Run()和Task.Delay()在一段时间后终止