Задача запланирована в том же потоке, что и вызывающая сторона.

В моем приложении есть модель представления, которая содержит поле Lazy<BitmapImage>. Поле заполняется с помощью служебного вызова на сервер. В случаях, когда изображение большое, серверу требуется несколько секунд, чтобы вернуть изображение (которое на самом деле является byte[]), поэтому пользовательский интерфейс блокируется. Чтобы предотвратить это, я поместил вызов службы в Task, чтобы фоновый поток получил изображение, а затем вызвал OnPropertyChanged, чтобы сообщить пользовательскому интерфейсу, что изображение возвращено:

Console.WriteLine("Outside Task ThreadID: {0}",
    Thread.CurrentThread.ManagedThreadId);

Task.Factory.StartNew(() =>
{
    Console.WriteLine("Inside Task ThreadID: {0}", Thread.CurrentThread.ManagedThreadId);

    return Utilities.ConvertByteToImage(
              SessionService.GetUserInformation(UserInfo.From).ProfilePicture);
}).ContinueWith(resultToken =>
{
    m_lazyProfilePicture = new Lazy<BitmapImage>(() =>
    {
        return (resultToken.Result == null) ? Utilities.DefaultProfilePicture.Value : resultToken.Result;
    });

    OnPropertyChanged("ProfilePicture");
});

Я заметил, что даже после помещения вызова службы в Task пользовательский интерфейс до сих пор заблокирован. Поэтому добавил эти строки Console.WriteLine, чтобы увидеть идентификаторы потоков. Удивительно, но оба они сообщают один и тот же идентификатор потока (похоже, это происходит только в этом случае. Я пробовал это с другими задачами в проекте, и все они сообщают разные идентификаторы). Есть идеи, что здесь происходит? Это как-то связано с BitmapImage? По какой-то причине планировщик решает поставить задачу в этот же поток, но я не понимаю, почему. Любые предложения приветствуются!


person PoweredByOrange    schedule 18.09.2013    source источник
comment
Я думаю, вы неправильно используете Lazy. Он предназначен для использования, когда делегат стоит дорого, и вы в порядке, когда он блокируется. Но в вашем случае делегат выполняется очень быстро. Вы могли бы использовать только BitmapImage в качестве типа поля.   -  person svick    schedule 19.09.2013


Ответы (1)


StartNew не гарантирует, что задача будет запущена в новом потоке. Он использует TaskScheduler.Current для планирования новой задачи. Во многих местах вашего кода это будет null. Когда это null, будет использоваться TaskScheduler.Default, что запланирует запуск делегата в пуле потоков.

В вашем конкретном случае Current не равно нулю. Это представление некоторого планировщика задач, который планирует запуск делегатов в потоке пользовательского интерфейса.

Это может произойти, если код, который вы выполняете, является результатом вызова StartNew или ContinueWith с контекстом синхронизации пользовательского интерфейса. Во время выполнения делегатов в любом случае текущий планировщик будет установлен на основе предоставленного SynchronizationContext, а именно контекста пользовательского интерфейса.

Если вы используете Task.Run, вы избегаете проблемы; он всегда будет использовать планировщик задач по умолчанию вместо текущего.

Другой вариант — явно указать, что вам нужен планировщик задач по умолчанию:

Task.Factory.StartNew(() => { }
    , CancellationToken.None
    , TaskCreationOptions.None
    , TaskScheduler.Default);
person Servy    schedule 18.09.2013
comment
Хорошо! Использование планировщика по умолчанию помогло. Сейчас вылезла другая проблема. Я не могу вызвать метод OnPropertyChanged. В нем говорится, что Must create DependencySource on same Thread as the DependencyObject. я пытался поместить TaskScheduler.FromCurrentSynchronizationContext в блок ContinueWith, но он говорит, что The current SynchronizationContext may not be used as a TaskScheduler. я также пытался отправить его как в диспетчере Application.Current, так и в диспетчере вызывающей стороны. Есть идеи? - person PoweredByOrange; 19.09.2013
comment
BitmapImage.Freeze() требовалось, поскольку изображение создавалось в другом потоке, но было привязано к потоку пользовательского интерфейса. Кроме того, you are running is the result of a call to StartNew or ContinueWith with the UI synchronization context было именно так. Большое спасибо! - person PoweredByOrange; 19.09.2013