如何在 STA 线程上强制任务 运行?

How to force a task to run on an STA thread?

我有一个 WinForms 应用程序,我想使用需要 STA 线程的 Windows.Forms.OpenFileDialog。它适用于线程,但任务呢?

相关问题,但没有找到解决方案:How to create a task (TPL) running a STA thread?

这是我正在尝试实现的示例(主要尝试了上面相关问题中所做的):

public async Task<string> TestFileDialogAsync()
{
    string outFile = "no file";

    await Task.Factory.StartNew(() =>
    {
        Console.WriteLine($"Thread apartment: {Thread.CurrentThread.ApartmentState}");
            // prints MTA
        try
        {
            using (OpenFileDialog myDialog = new OpenFileDialog()) 
            {
                myDialog.Title = "Choose a file...";
                myDialog.Filter = "All Files|*.*";

                if (myDialog.ShowDialog() == DialogResult.OK) // no dialog ever shown
                {
                    outFile = myDialog.FileName;
                }
            }
        }
        catch (Exception e) // no exception thrown - assuming task blocks in ShowDialog
        {
            Console.WriteLine($"Exception occured: {e.Message}");
            throw;
        }
    }, CancellationToken.None, TaskCreationOptions.None,
        TaskScheduler.FromCurrentSynchronizationContext());

    return outFile;
}

这被称为:

string chosenFile = await TestFileDialogAsync();

在我链接的问题中,类似的内容被标记为答案,但这对我不起作用。

我不明白 TaskScheduler.FromCurrentSynchronizationContext() 应该如何将任务强制到 STA 线程上的 运行,如相关我已经链接的问题。 (也许调用线程有一个 STA 线程开始,但任务是在 MTA 中开始的,这就是为什么它有效?)

那么如何在 STA 公寓状态下强制启动任务,以便我可以在我的应用程序中使用需要它的组件?

I have WinForms application, and I want to use the Windows.Forms.OpenFileDialog, which requires an STA thread.

是的。每个 WinForms 应用程序都有一个 STA 线程,它是它的主线程。只使用该线程是最简单的。

It works fine with threads, but what about tasks? ... How to create a task (TPL) running a STA thread?

单元线程模型是一个线程概念。没有 STA 任务这样的东西。

但是,委托任务可以 运行 在 STA 线程上,如果它被调度到该线程。

I don't understand how TaskScheduler.FromCurrentSynchronizationContext() is supposed to force a task to run on a STA thread

它并不总是这样做。 TaskScheduler.FromCurrentSynchronizationContext 将创建一个 TaskScheduler,将任务安排到 SynchronizationContext.Current。现在,在 WinForms 上,如果调用代码在 UI 线程 上,那么 SynchronizationContext.Current 是一个 WindowsFormsSynchronizationContext 实例。 WindowsFormsSynchronizationContext 将在 UI 线程上执行代码。因此,TaskScheduler 会将任务调度到 UI 线程(这是一个 STA 线程),任务最终 运行ning 在现有 STA 线程上。同样,如果 if TaskScheduler.FromCurrentSynchronizationContext 首先从 UI 线程调用。

如果TaskScheduler.FromCurrentSynchronizationContext是从线程池线程调用的,那么SynchronizationContext.Current就是null,结果TaskScheduler在线程池线程上调度任务,即不是 STA.

So how do I forcibly start a task on STA apartment state, so that I can use components that require it in my application?

执行此操作的最佳方法是构建代码,以便 UI 线程调用后台线程,而不是相反。不要让你的后台线程调用 UI 来做事。如果 UI 处于控制之中,则可以使用更简单的模式,如 awaitIProgress<T> 来与后台线程协调。如果后台线程 必须 驱动 UI,那么一种解决方案是捕获 UI SynchronizationContext(或 TaskScheduler)并从后台线程使用它在 UI 线程上执行代码。