Задание сна (System.Threading.Tasks)

Мне нужно создать поток, который заменит фотографию в окне Windows Forms, затем ждет ~1 секунду и восстановит предыдущую фотографию.

Я думал, что следующий код:

TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() =>
{
    pic.Image = Properties.Resources.NEXT;
    Thread.Sleep(1000);
    pic.Image = Properties.Resources.PREV;
}, CancellationToken.None, TaskCreationOptions.LongRunning, ui)

делать работу, но, к сожалению, нет. Замораживает основной поток пользовательского интерфейса.

Это потому, что не гарантируется наличие одного потока на одну задачу. Один поток можно использовать для обработки нескольких задач. Даже опция TaskCreationOptions.LongRunning не может помочь.

Как я могу это исправить?

3 ответа

Решение

Thread.Sleep - это синхронная задержка. Если вы хотите асинхронную задержку, используйте Task.Delay.

В C# 5, который в настоящее время находится в бета-версии, вы можете просто сказать

await Task.Delay(whatever);

в асинхронном методе, и метод автоматически выберет, где он остановился.

Если вы не используете C# 5, вы можете "вручную" установить любой код, который вы хотите, чтобы продолжить задержку самостоятельно.

Когда вы передаете новый 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);

Здесь мы создаем и запускаем задачу, которая будет выполняться в потоке пользовательского интерфейса, которая обновит Image собственностью pic с вашим ресурсом. Пока он это делает, пользовательский интерфейс будет не отвечать. К счастью, это, вероятно, очень быстрая операция, и пользователь даже не заметит.

task.ContinueWith(t => Thread.Sleep(1000), TaskScheduler.Default)
    .ContinueWith(t =>
                      {
                          pic.Image = Properties.Resources.Prev;
                      }, ui);

С помощью этого кода мы вызываем ContinueWith метод. Это именно то, что звучит. Возвращает новый Task объект, который будет выполнять лямбда-параметр при запуске. Он будет запущен, когда задача будет выполнена, неисправна или отменена. Вы можете контролировать, когда он будет работать, передавая TaskContinuationOptions, Однако мы также передаем другой планировщик задач, как и раньше. Это планировщик задач по умолчанию, который будет выполнять задачу в потоке пула потоков, таким образом, НЕ блокируя пользовательский интерфейс. Эта задача может выполняться часами, и ваш пользовательский интерфейс останется отзывчивым (не позволяйте этому), потому что это отдельный поток от потока пользовательского интерфейса, с которым вы взаимодействуете.

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

Вы должны использовать Timer выполнить задачу пользовательского интерфейса в какой-то момент в будущем. Просто установите его для запуска один раз, и с интервалом в 1 секунду. Поместите код пользовательского интерфейса в тиковое событие, а затем отключите его.

Если вы действительно хотите использовать задачи, вы бы хотели, чтобы другая задача выполнялась не в потоке пользовательского интерфейса, а в виде фоновой угрозы (то есть просто обычной StartNew задача), а затем используйте Control.Invoke внутри задачи для запуска команды в потоке пользовательского интерфейса. Проблема здесь заключается в том, что "бинты помогают решить основную проблему запуска задачи просто для того, чтобы она заснула. Лучше просто, чтобы код даже не выполнялся в первую очередь в течение полной секунды.

Другие вопросы по тегам