Параллельное программирование с использованием TPL на WinForms

Я пытался использовать TPL на WinForms .NET 4.0, я следовал этим шагам (перейдите к концу статьи), которые предназначены для WPF, и внес некоторые небольшие изменения, чтобы он мог работать на WinForms, но он по-прежнему не работает.. Он должен отображаться результат на метке и richTextBox, но это не так... Я думаю, что параллельный процесс заставляет мышь медленно двигаться некоторое время, когда я нажимаю кнопку..

public static double SumRootN(int root)
{   double result = 0;
    for (int i = 1; i < 10000000; i++)
    {   result += Math.Exp(Math.Log(i) / root);}
    return result;
}
private void button1_Click(object sender, EventArgs e)
{   richTextBox1.Text = "";
    label1.Text = "Milliseconds: ";
    var watch = Stopwatch.StartNew();
    List<Task> tasks = new List<Task>();
    for (int i = 2; i < 20; i++)
    {   int j = i;
        var t = Task.Factory.StartNew
          (   () =>
                {   var result = SumRootN(j);
                    Dispatcher.CurrentDispatcher.BeginInvoke
                        (new Action
                             (   () => richTextBox1.Text += "root " + j.ToString() 
                                   + " " + result.ToString() + Environment.NewLine
                             )
                         , null
                        );
                 }
            );
        tasks.Add(t);
    }
    Task.Factory.ContinueWhenAll
         (  tasks.ToArray()
            , result =>
                {   var time = watch.ElapsedMilliseconds;
                    Dispatcher.CurrentDispatcher.BeginInvoke
                          (   new Action
                                (    () =>
                                      label1.Text += time.ToString()
                                 )
                           );
                }
        );
}

3 ответа

Решение

Ваш код не будет работать, потому что пользовательский интерфейс потока для отображения результата полностью отличается от WPF. В WPF пользовательский интерфейс потока - Dispatcher, а в Windows Form - другой.

Я изменил ваш код, чтобы он работал.

    private void button1_Click(object sender, EventArgs e)
    {
        richTextBox1.Text = "";
        label1.Text = "Milliseconds: ";

        var watch = Stopwatch.StartNew();
        List<Task> tasks = new List<Task>();
        for (int i = 2; i < 20; i++)
        {
            int j = i;
            var t = Task.Factory.StartNew(() =>
            {
                var result = SumRootN(j);
                richTextBox1.Invoke(new Action(
                        () =>
                        richTextBox1.Text += "root " + j.ToString() + " " 
                              + result.ToString() + Environment.NewLine));
            });
            tasks.Add(t);
        }

        Task.Factory.ContinueWhenAll(tasks.ToArray(),
              result =>
              {
                  var time = watch.ElapsedMilliseconds;
                  label1.Invoke(new Action(() => label1.Text += time.ToString()));
              });
    }

Оставляя в стороне вопрос, было ли это хорошо делать с точки зрения обучения и упомянуто в комментариях тему "System.Windows.Threading.Dispatcher и WinForms?" с несколько запутанным ответом:

"Если вы уверены, что находитесь в потоке пользовательского интерфейса (например, в обработчике button.Click), Dispatcher.CurrentDispatcher предоставляет вам диспетчер потока пользовательского интерфейса, который вы можете использовать для отправки из фонового потока в поток пользовательского интерфейса как обычно"

уместно упомянуть, что ( также предоставил мой ответ на вышеупомянутый вопрос):

  • Task.Factory.StartNew() порождает выполнение в нескольких потоках, отличных от основного пользовательского интерфейса или его дочерних потоков
  • можно использовать Dispatcher на любом потоке
  • Приложение WPF OOTB (Out-of-thebox) обеспечивает System.Windows.Threading.Dispatcher DispatcherObject.Dispatcher UI потока отсутствует в формах Winfows
  • Используется в вопросе Dispatcher.CurrentDispatcher заставляет диспетчеров порождать задачи не-пользовательскими потоками

В любом случае, с дидактической точки зрения внесения минимальных изменений в исходный код WPF, вы должны были поймать и использовать диспетчер UI:

private void button1_Click(object sender, EventArgs e)
{   Dispatcher dispatcherUI = Dispatcher.CurrentDispatcher;//added **********
    richTextBox1.Text = "";
    label1.Text = "Milliseconds: ";
    var watch = Stopwatch.StartNew();
    List<Task> tasks = new List<Task>();
    for (int i = 2; i < 20; i++)
    {   int j = i;
        var t = Task.Factory.StartNew
          (   () =>
                {   var result = SumRootN(j);
      //Dispatcher.CurrentDispatcher.BeginInvoke//***changed to
                    dispatcherUI.BeginInvoke
                        (new Action
                             (   () => richTextBox1.Text += "root " + j.ToString() 
                                   + " " + result.ToString() + Environment.NewLine
                             )
                         , null
                        );
                 }
            );
        tasks.Add(t);
    }
    Task.Factory.ContinueWhenAll
         (  tasks.ToArray()
            , result =>
                {   var time = watch.ElapsedMilliseconds;
     //Dispatcher.CurrentDispatcher.BeginInvoke//**************changed to
                    dispatcherUI.BeginInvoke//added
                          (   new Action
                                (    () =>
                                      label1.Text += time.ToString()
                                 )
                           );
                }
        );
} 

Как описано в приведенной ниже ссылке, правильным способом было бы полностью исключить использование класса Dispatcher. Вместо этого следует создать соответствующий экземпляр TaskScheduler и передать его методам Task. http://blogs.msdn.com/b/csharpfaq/archive/2010/06/18/parallel-programming-task-schedulers-and-synchronization-context.aspx

То есть

Task.Factory.ContinueWhenAll(tasks.ToArray(),
      result =>
      {
          var time = watch.ElapsedMilliseconds;
          this.Dispatcher.BeginInvoke(new Action(() =>
              label1.Content += time.ToString()));
      });

станет

var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
    result =>
    {
        var time = watch.ElapsedMilliseconds;
        label1.Content += time.ToString();
    }, CancellationToken.None, TaskContinuationOptions.None, ui);
Другие вопросы по тегам