任务 - 如何确保 ContinueWith 操作是 STA 线程?

Posted

技术标签:

【中文标题】任务 - 如何确保 ContinueWith 操作是 STA 线程?【英文标题】:Tasks - how to ensure the ContinueWith action is an STA thread? 【发布时间】:2013-12-20 07:22:07 【问题描述】:

我正在尝试在需要在 Windows 2003 上运行的小型 .net 4.0 应用程序(如果需要,使用 Visual Studio 2010 编写)中使用 tasks 并使用带有调色板参数的 WriteableBitmap。

因此,使用所述类的代码必须作为 STA 线程运行,以避免它抛出 invalid cast exception(如果您有兴趣,请参阅 here 了解为什么我需要 STA 线程,但这不是我问题的重点)。

因此,我检查了堆栈溢出并遇到了 How to create a task (TPL) running a STA thread? 和 The current SynchronizationContext may not be used as a TaskScheduler - 完美,所以现在我知道该怎么做了,除了...

这是一个小控制台应用程序:

using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskPlayingConsoleApplication

    class Program
    
        [STAThread]
        static void Main()
        
            Console.WriteLine("Before Anything: " 
                + Thread.CurrentThread.GetApartmentState());

            SynchronizationContext.SetSynchronizationContext(
                new SynchronizationContext());
            var cts = new CancellationTokenSource();
            var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

            var task = Task.Factory.StartNew(
                () => Console.WriteLine(
                    "In task: " + Thread.CurrentThread.GetApartmentState()),
                cts.Token,
                TaskCreationOptions.None,
                scheduler);

            task.ContinueWith(t =>
                 Console.WriteLine(
                   "In continue: " + Thread.CurrentThread.GetApartmentState()),
                   scheduler);

            task.Wait();
        
    

这是它的输出:

Before Anything: STA 
In task: STA 
In continue: MTA

什么!?!是的,它回到了传递给ContinueWith 方法的Action<Task> 上的MTA 线程。

我将 same 调度程序传递给任务和继续,但不知何故在继续中它似乎被忽略了。

我确定这很愚蠢,那么如何确保传递给ContinueWith 的回调使用 STA 线程?

【问题讨论】:

我打算自动写你可能忘记指定调度程序,但所有这些都已经到位......奇怪。 那些结果很奇怪,我得到了不同的结果。我希望“任务中”返回 MTA(它对我来说确实如此),因为 SynchronizationContext 的默认实现使用 .NET 线程池。 ThreadPool 线程默认为 MTA。 @mikez:是的,这也让我感到惊讶。这就是为什么我认为第一个任务是在 StartNew 发出的那一刻同步运行的,但我想不出为什么会这样,为什么后来的 ContinueWith 没有以同样的方式处理.. 【参考方案1】:

编辑:在您阅读以下任何内容之前,这里有一篇出色的主题文章:http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx;你可以跳过我的帖子直接去!

描述根本原因的最重要部分:

SynchronizationContext.Post 的默认实现只是转身并通过 QueueUserWorkItem将其传递给 ThreadPool。但是 (...) 可以从 SynchronizationContext 派生出自己的上下文并覆盖 Post 方法以更适合所表示的调度程序。

例如,对于 Windows 窗体,WindowsFormsSynchronizationContext 实现 Post 以将委托传递给 Control.BeginInvoke。对于 WPF 中的 DispatcherSynchronizationContext,它调用 Dispatcher.BeginInvoke。以此类推。

因此,您需要使用基础 SynchronizationContext 类以外的其他东西。尝试使用任何其他现有的,或创建自己的。示例包含在文章中。


现在,我的原始回复:

经过一番思考,我认为问题在于您的控制台应用程序中没有“消息泵”之类的东西。默认的 SynchronizationContext 只是一个锁。它防止线程在资源上相交,但不提供任何排队或线程选择。通常,您打算将 SynchroContext 子类化以提供您自己的正确同步方式。 WPF 和 WinForms 都提供了自己的子类型。

当您Wait 处理您的任务时,很可能 MainThread 被阻塞,而所有其他线程都在默认线程池中的一些随机线程上运行。

请尝试将线程 ID 与 STA/MTA 标志一起写入控制台。

你可能会看到:

STA: 1111
STA: 1111
MTA: 1234

如果你看到这个,那么你的第一个任务很可能是在调用线程上同步运行并立即完成,然后你尝试“继续”它只是'附加到“队列”,但它没有立即启动(猜想,我不知道为什么会这样;旧任务已经完成,所以 ContinueWith 也可以同步运行它)。然后主线程被锁定等待,并且由于没有消息泵 - 它无法切换到另一个作业并休眠。然后线程池等待并扫除挥之不去的延续任务。不过只是猜测。您可以尝试通过

进行检查
prepare synccontext
write "starting task1"
start task1 ( -> write "task1")
write "continuing task2"         <--- add this one 
continue: task2 ( -> write "task2")
wait

并检查日志中消息的顺序。任务1的“你好”之前是否“继续”?

您也可以尝试看看如果您不通过 StartNew 创建 Task1 会发生什么,而是将其创建为准备/暂停,然后继续,然后启动,然后等待。如果我对同步运行的看法是正确的,那么在这样的设置中,主任务和延续任务要么都在调用的“1111”STA 线程上运行,要么都在线程池的“2222”线程上运行。

同样,如果所有这些都是正确的,那么提供一些消息泵和适当的 SyncContext 类型可能会解决您的问题。正如我所说,WPF 和 WinForms 都提供了它们自己的子类型。虽然我现在不记得名字了,但你可以尝试使用它们。如果我没记错的话,WPF 会自动启动它的调度程序,你不需要任何额外的设置。我不记得 WinForms 怎么样了。但是,随着 WPF 的自动启动,如果您的 ConsoleApp 实际上是某种将运行许多单独案例的单元测试,您将需要在案例之前关闭 WPF 的调度程序.. 但这就是现在离主题很远。

【讨论】:

我认为你很成功 - 我输入了额外的消息,实际上,前两个条目在同一个线程上。好的,那么我需要对 SynchroContext 这件事做更多的研究......谢谢! 也请参阅此答案:***.com/a/14144101/717732 虽然总体而言问题有点不同,但其中描述的问题相似 啊,终于来了blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx 非常感谢 - 基于这篇文章和链接的文章,我制作了自己的小 SynchronizationContext 并将其挂钩,并确保所有内容都在同一个线程上调用,这对于我的控制台应用程序来说已经足够了! @Simon_Weaver - 我在一个单元测试中使用了它,我为不久前在 github 上的一个库编写了这个库 - 看看 nsane 存储库中的内部类 SingleThreadSynchronizationContext。

以上是关于任务 - 如何确保 ContinueWith 操作是 STA 线程?的主要内容,如果未能解决你的问题,请参考以下文章

组合ContinueWith

[C#]Task.ContinueWith使用实例

[C#]Task.ContinueWith使用实例

使用 SemaphoreSlim 和 Continuewith 下载 URL

Task_等待任务完成几种方式

Task.ContinueWith 和异步(核心 3.1)