Резервные копии задач. Run и UI

Этот фрагмент кода находится в блоге Стивена Клири и дает пример того, как сообщать о прогрессе при использовании Task.Run. Я хотел бы знать, почему нет проблем с перекрестными потоками при обновлении пользовательского интерфейса, и я имею в виду, почему вызов не требуется?

private async void button2_Click(object sender, EventArgs e)
{
    var progressHandler = new Progress<string>(value =>
    {
        label2.Text = value;
    });
    var progress = progressHandler as IProgress<string>;
    await Task.Run(() =>
    {
        for (int i = 0; i != 100; ++i)
        {
            if (progress != null)
                progress.Report("Stage " + i);
            Thread.Sleep(100);
        }
    });
    label2.Text = "Completed.";
}

Ответ 1

Progress<T> захватывает текущий SynchronisationContext при его создании. Всякий раз, когда вы вызываете Report, он тайно делегирует это в захваченный контекст. В этом примере захваченным контекстом является пользовательский интерфейс, что означает, что исключений не происходит.

Ответ 2

Конструктор Progress<T> фиксирует текущий объект SynchronizationContext.

Класс SynchronizationContext - это средство, которое абстрагирует детали задействованной модели потоков. То есть, в Windows Forms он будет использовать Control.Invoke, в WPF он будет использовать Dispatcher.Invoke и т.д.

Когда вызывается объект progress.Report, сам объект Progress знает, что он должен запускать свой делегат, используя захваченный SynchronizationContext.

В других терминах это работает, потому что Progress был разработан для обработки этого, если разработчик не должен явно говорить об этом.

Ответ 3

Кажется, вы сбиты с толку из-за того, что часть этого поперечного механизма скрыта от глаз разработчика, поэтому вам просто нужно "взять и использовать": http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx

Мы ввели интерфейс IProgress, чтобы вы могли создать опыт для отображения прогресса. Этот интерфейс предоставляет отчет (T) метод, который асинхронная задача вызывает для отчета о прогрессе. Вы раскрываете это интерфейс в сигнатуре асинхронного метода, а вызывающий предоставить объект, реализующий этот интерфейс. Вместе задача и вызывающий пользователь создает очень полезную ссылку (и может работать на разные потоки).

Мы также предоставили класс Progress, который представляет собой реализацию IProgress. Вам предлагается использовать Прогресс в вашем реализации, поскольку он обрабатывает всю бухгалтерию с сохранением и восстановление контекста синхронизации. Прогресс раскрывает как событие и обратный вызов Action, которые вызывается, когда задача сообщает прогресс. Этот шаблон позволяет вам писать код, который просто реагирует на изменения прогресса по мере их возникновения. Вместе, IProgress и Прогресс обеспечивает простой способ передачи информации о ходе работы с фоновая задача для потока пользовательского интерфейса.

Еще одно замечание: уведомление о ходе выполнения будет вызвано после, часть выполнения задания не только в этот момент. Итак, если ваш поток пользовательского интерфейса работает на холостом ходу, и у вас есть запасной процессорный ядро, задержка будет почти нулевой. Если ваш поток пользовательского интерфейса занят, уведомление не будет вызываться до тех пор, пока поток пользовательского интерфейса не вернется в режим ожидания (независимо от того, сколько резервных ядер процессора имеет ваш компьютер).