我试图在一个小的.net 4.0应用程序(使用Visual Studio 2010编写,如果这很重要)中使用任务,需要在Windows 2003上工作并使用带调色板参数的WriteableBitmap.
因此,使用所述类的代码必须作为STA线程运行,以避免它抛出无效的强制转换异常(如果您感兴趣,请参阅此处了解我需要STA线程的原因,但这不是我的问题的主旨).
因此,我检查了堆栈溢出并遇到了如何创建运行STA线程的任务(TPL)?和目前的SynchronizationContext可能不被用作的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
什么了!?!是的,它回到了传递给方法的MTA线程.Action
ContinueWith
我将相同的调度程序传递给任务并继续但不知何故在继续它似乎被忽略.
我确定它是愚蠢的,所以我如何确保我的回调传递给ContinueWith使用STA线程?
编辑:在您阅读以下任何内容之前,这是一篇优秀的主题文章: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
并检查日志中的消息顺序.从task1开始"hello"之前是"继续"吗?
您也可以尝试看看如果您不通过StartNew创建Task1会发生什么,而是将其创建为准备/暂停,然后继续,然后启动,然后等待.如果我对同步运行是正确的,那么在这样的设置中,main和continuation任务将既可以在调用'1111'STA线程上运行,也可以在线程池'2222'线程上运行.
同样,如果所有这些都是正确的,那么提供一些消息泵和正确的SyncContext类型可能会解决您的问题.正如我所说,WPF和WinForms都提供了自己的子类型.虽然我现在不记得这些名字,但您可以尝试使用它们.如果我没记错的话,WPF会自动启动它的调度程序,你不需要任何额外的设置.我不记得WinForms是怎么回事.但是,在WPF自动启动的情况下,如果您的ConsoleApp实际上是某种单元测试,它将运行许多单独的案例,您需要在案例之前关闭WPF的调度程序..但现在远离主题.