以下是一些显示意外行为的简单代码:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_UI = TaskScheduler.FromCurrentSynchronizationContext();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
TaskScheduler _UI;
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
//Expected: Worker thread
//Found: Worker thread
DoSomething();
})
.ContinueWith(t =>
{
//Expected: Main thread
//Found: Main thread
DoSomething();
Task.Factory.StartNew(() =>
{
//Expected: Worker thread
//Found: Main thread!!!
DoSomething();
});
}, _UI);
}
void DoSomething()
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
}
}
为什么内部任务在主线程中执行? 我该如何防止这种行为?
答案 0 :(得分:6)
不幸的是,当您运行续集时,当前任务计划程序将成为SynchronizationContextTaskScheduler
的{{1}}设置。
这在this Connect Bug中讨论过 - 并且在.NET 4中以这种方式按设计编写。但是,我同意这种行为在这里有点不尽如人意。
您可以通过在构造函数中抓取“后台”调度程序并使用它来解决此问题:
TaskScheduler.FromCurrentSynchronizationContext
完成后,您可以轻松地安排“背景”任务:
TaskScheduler _UI;
// Store the default scheduler up front
TaskScheduler _backgroundScheduler = TaskScheduler.Default;
public MainWindow()
{
InitializeComponent();
_UI = TaskScheduler.FromCurrentSynchronizationContext();
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
此外,在这种情况下,由于您的操作已结束,您可以将其置于自己的延续中并获得所需的行为。但是,这不是一个“通用”解决方案,但在这种情况下 :
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
//Expected: Worker thread
//Found: Worker thread
DoSomething();
})
.ContinueWith(t =>
{
//Expected: Main thread
//Found: Main thread
DoSomething();
// Use the _backgroundScheduler here
Task.Factory.StartNew(() =>
{
DoSomething();
}, CancellationToken.None, TaskCreationOptions.None, _backgroundScheduler);
}, _UI);
}
答案 1 :(得分:1)
除了@ reed-copsey的好答案之外,我想补充一点,如果你想强制你的任务在线程池线程上执行,你也可以使用TaskScheduler.Default属性,该属性总是引用ThreadPoolTaskScheduler:
return Task.Factory.StartNew(() =>
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
这样你就不必在@ reed-copsey的答案中提出的变量中捕获任务调度程序。
有关TaskSchedulers的更多信息,请访问:TaskSchedulers on MSDN
答案 2 :(得分:0)
如果您想确保您的内部任务不在UI线程上运行,只需将其标记为LongRunning
:
Task.Factory.StartNew(() =>
{
//Expected: Worker thread
//Found: Main thread!!!
DoSomething();
}, TaskCreationOptions.LongRunning);
这应该确保它被赋予自己的线程而不是在当前线程上内联。