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

Dan*_*ant 5 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Ree*_*sey 6

不幸的是,当你正在运行你的延续时,当前的任务调度程序将成为SynchronizationContextTaskScheduler你的设置TaskScheduler.FromCurrentSynchronizationContext.

这是在讨论这个连接错误 -而写这样的设计在.NET 4.但是,我认为,行为极不期望在这里一点.

您可以通过在构造函数中抓取"后台"调度程序并使用它来解决此问题:

TaskScheduler _UI;

// Store the default scheduler up front
TaskScheduler _backgroundScheduler = TaskScheduler.Default; 

public MainWindow()
{
    InitializeComponent();

    _UI = TaskScheduler.FromCurrentSynchronizationContext();
    Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
Run Code Online (Sandbox Code Playgroud)

完成后,您可以轻松地安排"背景"任务:

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);
}
Run Code Online (Sandbox Code Playgroud)

此外,在这种情况下,由于您的操作已结束,您可以将其置于自己的延续中并获得所需的行为.然而,这不是"通用"解决方案,但在这种情况下有效:

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

        }, _UI)
    .ContinueWith(t =>
            {
                //Expected: Worker thread
                //Found: Is now worker thread
                DoSomething();
            });
}
Run Code Online (Sandbox Code Playgroud)