Задачата се планира в същата нишка като тази на обаждащия се

Приложението ми има модел на изглед, който съдържа поле 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