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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15998459/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 08:35:50  来源:igfitidea点击:

Parallel Programming using TPL on WinForms

c#wpfwinformstask-parallel-library

提问by a1204773

I trying to use TPL on WinForms .NET 4.0, I followed thissteps (go to the end of article) that are for WPF and made some small changes so it could work on WinForms but it still doesn't work.. It should display result on label and richTextBox but it not... I think the parallel process work cause mouse start moving slow for a while when I click the button..

我尝试在 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()
                                 )
                           );
                }
        );
}

回答by Toan Vo

Your code will not work because the thread UI to display result totally different with WPF. With WPF the thread UI is Dispatcher but on Windows Form is another one.

您的代码将无法工作,因为显示结果的线程 UI 与 WPF 完全不同。使用 WPF,线程 UI 是 Dispatcher,但在 Windows 窗体上是另一个。

I have modified your code to help it work.

我已经修改了您的代码以帮助它工作。

    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()));
              });
    }

回答by Gennady Vanin Геннадий Ванин

Leaving aside the whether it was good to do this way, from the learning angle and mentioned in comments topic "System.Windows.Threading.Dispatcher and WinForms?" with somewhat confusing answer:

撇开这样做是否好,从学习的角度和评论主题中提到的“System.Windows.Threading.Dispatcher 和 WinForms?” 与有些混乱的答案

"If you are sure to be in UI thread (eg. in an button.Click handler), Dispatcher.CurrentDispatcher gives you the UI thread dispatcher that you can use to dispatch from background thread to UI thread as usual"

“如果您确定在 UI 线程中(例如在 button.Click 处理程序中),Dispatcher.CurrentDispatcher 会为您提供 UI 线程调度程序,您可以像往常一样使用它从后台线程调度到 UI 线程”

it is pertinent to mention that (also provided my answerto mentioned above question):

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

  • Task.Factory.StartNew()spawns executions on multiple threads different from the main UI or its child threads
  • it is possible to use Dispatcheron any thread
  • WPF application OOTB (Out-of-thebox) provides System.Windows.Threading.Dispatcher DispatcherObject.Dispatcherof UI thread absent in Winfows forms
  • Used in question Dispatcher.CurrentDispatcheris getting the dispatchers on spawned by tasks non-ui threads
  • Task.Factory.StartNew()在不同于主 UI 或其子线程的多个线程上产生执行
  • 可以在任何线程上 使用Dispatcher
  • WPF 应用程序 OOTB(开箱即用)提供 System.Windows.Threading.Dispatcher DispatcherObject.Dispatcher了 Winfows 表单中不存在的 UI 线程
  • 使用的问题Dispatcher.CurrentDispatcher是让调度程序由非 ui 线程的任务产生

Anyway, from the didactic point of view of making minimum changes to the original WPF code, you should have caught and used UI dispatcher:

无论如何,从对原始 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()
                                 )
                           );
                }
        );
} 

回答by DanielS

As described in the link below, the correct way would be to eliminate the use of Dispatcher class altogether.Instead, you should create a relevant instance of TaskScheduler and pass it to the Task methods. http://blogs.msdn.com/b/csharpfaq/archive/2010/06/18/parallel-programming-task-schedulers-and-synchronization-context.aspx

如以下链接所述,正确的方法是完全消除 Dispatcher 类的使用。相反,您应该创建 TaskScheduler 的相关实例并将其传递给 Task 方法。 http://blogs.msdn.com/b/csharpfaq/archive/2010/06/18/parallel-programming-task-schedulers-and-synchronization-context.aspx

That is

那是

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

would become

会成为

var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.ContinueWhenAll(tasks.ToArray(),
    result =>
    {
        var time = watch.ElapsedMilliseconds;
        label1.Content += time.ToString();
    }, CancellationToken.None, TaskContinuationOptions.None, ui);