使用TPL在WinForms上进行并行编程

4

我正在尝试在WinForms .NET 4.0上使用TPL,我按照此页面上的步骤(跳到文章末尾)进行操作,这些步骤适用于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
Dispatcher与WPF有关,你不应该在WinForms中使用它。 请参阅https://dev59.com/gHVC5IYBdhLWcg3wZwXj。 - omer schleifer
调度程序与任何应用程序中的任何线程相关。WPF表单具有DispatcherObject,可访问UI线程的Dispatcher - Gennady Vanin Геннадий Ванин
3个回答

5

暂且不论这种做法是否好,从学习角度来看,在评论主题“System.Windows.Threading.Dispatcher和WinForms?”中提到了一个有些令人困惑的答案

“如果你确定在UI线程中(例如在button.Click处理程序中),Dispatcher.CurrentDispatcher会给你UI线程分派器,你可以像往常一样从后台线程分派到UI线程”

值得一提的是(我也回答了上述问题):

  • Task.Factory.StartNew() 可以在不同于主UI或其子线程的多个线程上生成执行
  • 可以在任何线程上使用Dispatcher
  • WPF应用程序OOTB(开箱即用)提供了在Winfows表单中缺少的UI线程的System.Windows.Threading.Dispatcher DispatcherObject.Dispatcher
  • 在问题中使用Dispatcher.CurrentDispatcher获取由任务非UI线程生成的调度程序

无论如何,从教学角度来看,为了对原始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()
                                 )
                           );
                }
        );
} 

4

你的代码无法正常工作,因为显示结果的线程UI与WPF完全不同。 在WPF中,线程UI是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()));
              });
    }

谢谢,它运行得很好...鼠标移动又慢了,但我认为这是正常的行为... - a1204773

0
如下链接所述,正确的方法是完全消除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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接