C# WPF中的并行任务

C# WPF中的并行任务,c#,wpf,parallel-processing,async-await,task-parallel-library,C#,Wpf,Parallel Processing,Async Await,Task Parallel Library,我试图在WPF中实现并行任务,但它似乎不起作用。我有一个非常简单的应用程序,它有一个按钮、一个标签,执行以下操作: 用户单击按钮 标签将更新以显示应用程序正在工作 两个辅助方法并行运行。每个worker方法访问一个UI元素作为其算法的一部分 完成这两个辅助方法后,标签将更新,以显示每个方法的运行时间以及应用程序运行的总时间 当我运行程序时,我得到以下结果: Worker 1 Time: 00:00:00.2370431 Worker 2 Time: 00:00:00.5505538 Total

我试图在WPF中实现并行任务,但它似乎不起作用。我有一个非常简单的应用程序,它有一个按钮、一个标签,执行以下操作:

  • 用户单击按钮
  • 标签将更新以显示应用程序正在工作
  • 两个辅助方法并行运行。每个worker方法访问一个UI元素作为其算法的一部分
  • 完成这两个辅助方法后,标签将更新,以显示每个方法的运行时间以及应用程序运行的总时间
  • 当我运行程序时,我得到以下结果:

    Worker 1 Time: 00:00:00.2370431
    Worker 2 Time: 00:00:00.5505538
    Total Time: 00:00:00.8334426
    
    这使我相信辅助方法可能是并行运行的,但是有东西以同步方式阻塞了应用程序。我认为如果它们真的并行运行,那么总时间应该接近运行时间最长的worker方法时间。我遇到的一个问题是,因为我正在访问worker方法中的UI元素,所以我需要使用
    Dispatcher.Invoke()
    方法

    注意:我阅读了上的文档。它指出,“为了更好地控制任务执行或从任务返回值,您必须更明确地使用任务对象。”这就是我隐式运行任务的原因;我没有从worker方法返回任何东西,我不需要更大的控制(至少我认为我不需要)

    我假设应用程序并行运行worker方法,并且有什么东西迫使应用程序以同步方式运行,这对吗?为什么我得到的是工人方法总时间的总和

    XAML

    
    
    C#

    名称空间WpfApp1
    {
    使用系统诊断;
    使用System.Threading.Tasks;
    使用System.Windows;
    使用System.Windows.Threading;
    公共部分类主窗口
    {
    私有字符串_t1;
    私有字符串_t2;
    私人秒表;
    公共主窗口()
    {
    初始化组件();
    }
    专用异步无效按钮1\u单击(对象发送方,路由目标)
    {
    _sw=新秒表();
    _sw.Start();
    标签1.Content=“工作…”;
    等待任务。运行(()=>
    {
    并行调用(Work1,Work2);
    Dispatcher.Invoke(UpdateLabel);
    });
    }
    私有void UpdateLabel()
    {
    _sw.Stop();
    Label1.Content=$“T1:{u T1}\nT2:{u t2}\n总计:{u sw.appeased}”;
    }
    私有无效工作1()
    {
    Dispatcher.Invoke(()=>
    {
    var text=按钮1.内容;
    var stopWatch=新秒表();
    秒表。开始();
    对于(var i=0;i<100000000;i++){
    _t1=秒表.appeased.ToString();
    },DispatcherPriority.ContextIdle);
    }
    私人工作2()
    {
    Dispatcher.Invoke(()=>
    {
    var text=按钮1.内容;
    var stopWatch=新秒表();
    秒表。开始();
    对于(var i=0;i<250000000;i++){}
    _t2=秒表.appeased.ToString();
    },DispatcherPriority.ContextIdle);
    }
    }
    }
    
    Dispatcher本质上是将工作编组到特定线程。您上下文中的线程很可能是UI线程

    那么您的代码在做什么:

  • 对可用线程池线程执行的
    Work1
    Work2
    队列
  • Work1
    Work2
    中,将工作整理到UI线程
  • 结果-所有计算繁重的工作将在单个线程上执行。这就是UI线程

    我假设应用程序并行运行worker方法,并且有什么东西迫使应用程序以同步方式运行,这对吗

    是的,工作方法正在不同的线程上运行。此外,您还可以正确地理解“有什么东西正在迫使应用程序以同步方式运行”,即
    Dispatcher.Invoke
    it将工作编组到UI线程。这意味着作为委托传递的所有计算繁重的工作都将在单个线程(UI线程)上执行。当从不同的线程调用辅助方法时,工作是在UI线程上完成的

    为什么我得到的是工人方法总时间的总和


    因为worker方法主体是在单个线程上执行的。

    正如大家已经说过的,您在UI线程中执行“后台”工作,因为您以错误的方式使用了
    Dispatcher
    对象。您应该仅在UI事件中使用它。因此,您可以在代码中做一些更好的事情:

  • 由于您有一个
    async
    事件处理程序,您可以在那里使用面向用户界面的代码,而无需使用
    Dispatcher
    ,只需将其从
    wait
    构造中移出即可:

    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
        _sw = new Stopwatch();
        _sw.Start();
        Label1.Content = "Working...";
    
        // save the context and return here after task is done
        await Task.Run(() =>
        {
            Parallel.Invoke(Work1, Work2);
        });
        // back on UI-context here
        UpdateLabel();
    }
    
  • 您的“后台”代码使用
    按钮1.Content
    属性,您也可以在事件处理程序中获得该属性,而无需使用
    调度程序。之后,您可以通过将参数传递给后台操作来创建lambdas:

    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
        _sw = new Stopwatch();
        _sw.Start();
        Label1.Content = "Working...";
        var buttonContent = ((Button) sender).Content
        // this will work too
        // consider to rename your button
        // var buttonContent = Button1.Content
    
        // save the context and return here after task is done
        await Task.Run(() =>
        {
            Parallel.Invoke(() => Work1(buttonContent),
                            () => Work2(buttonContent));
        });
        // back on UI-context here
        UpdateLabel();
    }
    
  • 现在您可以再次删除
    调度程序
    ,这一次您可以在
    工作#
    方法中执行此操作(因为您现在不需要从UI获取任何信息):


  • 您正在调度一个线程池线程,让它调度两个线程池线程,这两个线程都只是要求UI线程执行一系列工作,让线程池线程等待这些工作完成,然后让外部线程池线程等待这些工作完成。不要那样做。如果你想在UI线程中运行两件事,只需在UI线程中运行它们,不要安排3个线程池线程坐在那里无所事事,而在UI线程中执行2件事。如果我理解你的意思,我应该将UI线程放在
    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
        _sw = new Stopwatch();
        _sw.Start();
        Label1.Content = "Working...";
    
        // save the context and return here after task is done
        await Task.Run(() =>
        {
            Parallel.Invoke(Work1, Work2);
        });
        // back on UI-context here
        UpdateLabel();
    }
    
    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
        _sw = new Stopwatch();
        _sw.Start();
        Label1.Content = "Working...";
        var buttonContent = ((Button) sender).Content
        // this will work too
        // consider to rename your button
        // var buttonContent = Button1.Content
    
        // save the context and return here after task is done
        await Task.Run(() =>
        {
            Parallel.Invoke(() => Work1(buttonContent),
                            () => Work2(buttonContent));
        });
        // back on UI-context here
        UpdateLabel();
    }
    
    private void Work1(string text)
    {
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        for (var i = 0; i < 100000000; i++)
        {
             // real work here
        }
        _t1 = stopWatch.Elapsed.ToString();
    }
    
    private void Work2(string text)
    {
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        for (var i = 0; i < 100000000; i++)
        {
             // real work here
        }
        _t2 = stopWatch.Elapsed.ToString();
    }
    
    private async void Button1_Click(object sender, RoutedEventArgs e)
    {
        _sw = new Stopwatch();
        _sw.Start();
        Label1.Content = "Working...";
        var buttonContent = ((Button) sender).Content
    
        var w1 = Task.Run(() => Work1(buttonContent));
        var w2 = Task.Run(() => Work2(buttonContent));
    
        await Task.WhenAll(w1, w2);
    
        // back on UI-context here
        UpdateLabel();
    }