内部任务在意外的线程中执行

时间:2011-09-07 18:37:10

标签: task-parallel-library

以下是一些显示意外行为的简单代码:

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());
    }
}

为什么内部任务在主线程中执行? 我该如何防止这种行为?

3 个答案:

答案 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属性,该属性总是引用ThreadPoolTask​​Scheduler:

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);

这应该确保它被赋予自己的线程而不是在当前线程上内联。