Внутреннее задание выполнено в неожиданном потоке - PullRequest
5 голосов
/ 07 сентября 2011

Вот небольшой фрагмент кода, демонстрирующий неожиданное поведение:

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 ]

6 голосов
/ 07 сентября 2011

К сожалению, текущий планировщик задач, когда вы запускаете продолжение, становится настройкой 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);
}

Как только вы это сделаете, вы можете легко запланировать свой «фон»"задача соответственно:

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

Кроме того, в этом случае, поскольку ваша операция заканчивается, вы можете просто поместить ее в свое собственное продолжение и получить желаемое поведение.Это, однако, не решение общего назначения, но оно работает в данном случае :

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();
            });
}
1 голос
/ 27 февраля 2013

Помимо отличного ответа @ 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 на MSDN

0 голосов
/ 07 сентября 2011

Если вы хотите, чтобы ваша внутренняя задача не выполнялась в потоке пользовательского интерфейса, просто пометьте ее как LongRunning:

Task.Factory.StartNew(() =>
            {
                //Expected: Worker thread
                //Found: Main thread!!!
                DoSomething();
            }, TaskCreationOptions.LongRunning);

Это должно гарантировать, что ей будет присвоен собственный поток вместо встраивания натекущая тема.

...