Когда вы передаете новый TaskScheduler из текущего контекста синхронизации, вы фактически указываете задаче выполняться в потоке пользовательского интерфейса. Вы на самом деле хотите это сделать, поэтому вы можете обновить компонент пользовательского интерфейса, однако вы не хотите спать в этом потоке, так как он будет заблокирован.
Это хороший пример идеального случая .ContinueWith
:
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
РЕДАКТИРОВАТЬ (удалил кое-что и добавил это):
Что происходит, так это то, что мы блокируем поток пользовательского интерфейса только на время, достаточное для обновления pic.Image
. Указав TaskScheduler
, вы указываете, в каком потоке выполнять задачу. Важно знать, что отношения между задачами и потоками не равны 1:1. На самом деле, вы можете иметь 1000 задач, работающих в относительно небольшом количестве потоков, даже 10 или меньше, все зависит от объема работы, которую выполняет каждая задача. Не думайте, что каждая создаваемая вами задача будет выполняться в отдельном потоке. CLR отлично справляется с автоматической балансировкой производительности.
Теперь вам не нужно использовать TaskScheduler
по умолчанию, как вы видели. Когда вы передаете пользовательский интерфейс TaskScheduler
, то есть TaskScheduler.FromCurrentSynchronizationContext()
, он использует поток пользовательского интерфейса вместо пула потоков, как это делает TaskScheduler.Default
.
Имея это в виду, давайте еще раз просмотрим код:
var task = Task.Factory.StartNew(() =>
{
pic.Image = Properties.Resources.NEXT;
},
CancellationToken.None,
TaskCreationOptions.None,
ui);
Здесь мы создаем и запускаем задачу, которая будет выполняться в потоке UI и обновлять свойство Image
элемента pic
с помощью вашего ресурса. При этом UI не отвечает. К счастью, это, вероятно, очень операция, и пользователь даже не заметит.
task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
.ContinueWith(t =>
{
pic.Image = Properties.Resources.Prev;
}, ui);
С помощью этого кода мы вызываем метод ContinueWith
. Он делает именно то, на что это похоже. Он возвращает новый объект Task
, который будет выполнять лямбда-параметр при запуске. Он будет запущен, когда задача будет завершена, неисправна или отменена. Вы можете контролировать, когда он будет запущен, передав TaskContinuationOptions
. Однако мы также передаем другой планировщик задач, как и раньше. Это планировщик задач по умолчанию, который будет выполнять задачу в потоке пула потоков, таким образом, НЕ блокируя пользовательский интерфейс. Эта задача может выполняться часами, и ваш пользовательский интерфейс будет оставаться отзывчивым (не позволяйте этому), потому что это отдельный поток от потока пользовательского интерфейса, с которым вы взаимодействуете.
Мы также вызвали ContinueWith
для задач, которые мы установили для запуска в планировщике задач по умолчанию. Это задача, которая снова обновит изображение в потоке пользовательского интерфейса, поскольку мы передали тот же планировщик задач пользовательского интерфейса в выполняемую задачу. Как только задача пула потоков завершится, она вызовет эту задачу в потоке пользовательского интерфейса, блокируя ее на очень короткий период времени, пока изображение обновляется.
person
Christopher Currens
schedule
10.04.2012
FromCurrentSynchronizationContext()
, который для WinForms будет потоком пользовательского интерфейса. Таким образом, ваша задача в конечном итоге выполняется в потоке пользовательского интерфейса, который вы затем переводите в спящий режим. Не переводите поток пользовательского интерфейса в спящий режим. Никогда. - person dlev   schedule 11.04.2012