【发布时间】:2017-06-28 00:43:04
【问题描述】:
我正在尝试在 WPF 中实现并行任务,但它似乎不起作用。我有一个非常简单的应用程序,它有一个按钮、一个标签,它执行以下操作:
- 用户点击按钮。
- 标签已更新,表明应用正在运行。
- 两个工作方法并行运行。每个工作方法都会访问作为其算法的一部分的 UI 元素。
- 两个工作方法完成后,标签会更新,以显示每个方法运行所用的时间以及应用运行的总时间。
当我运行程序时,我得到以下结果:
Worker 1 Time: 00:00:00.2370431
Worker 2 Time: 00:00:00.5505538
Total Time: 00:00:00.8334426
这让我相信工作方法可能是并行运行的,但是某些东西以同步方式阻塞了应用程序。我认为如果它们真正并行运行,总时间应该接近最长运行的工作方法时间。我遇到的一个问题是,因为我在工作方法中访问 UI 元素,所以我需要使用 Dispatcher.Invoke() 方法。
注意:我阅读了Task-based Asynchronous Programming 上的文档。它指出,“为了更好地控制任务执行或从任务返回值,您必须更明确地使用任务对象。”这就是我隐式运行任务的原因;我没有从工作方法返回任何东西,我不需要更大的控制权(至少我认为我不需要)。
我是否正确假设应用程序正在并行运行工作方法并且某些东西正在迫使应用程序以同步方式运行?为什么我会得到总时间的工作方法时间的总和?
XAML
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow"
WindowStartupLocation="CenterScreen"
ResizeMode="NoResize"
Height="150"
Width="300">
<StackPanel>
<Button x:Name="Button1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Click Me"
Click="Button1_Click" />
<Label x:Name="Label1"
Content=" " />
</StackPanel>
</Window>
C#
namespace WpfApp1
{
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
public partial class MainWindow
{
private string _t1;
private string _t2;
private Stopwatch _sw;
public MainWindow()
{
InitializeComponent();
}
private async void Button1_Click(object sender, RoutedEventArgs e)
{
_sw = new Stopwatch();
_sw.Start();
Label1.Content = "Working...";
await Task.Run(() =>
{
Parallel.Invoke(Work1, Work2);
Dispatcher.Invoke(UpdateLabel);
});
}
private void UpdateLabel()
{
_sw.Stop();
Label1.Content = $"T1: {_t1}\nT2: {_t2}\nTotal: {_sw.Elapsed}";
}
private void Work1()
{
Dispatcher.Invoke(() =>
{
var text = Button1.Content;
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 100000000; i++) { }
_t1 = stopWatch.Elapsed.ToString();
}, DispatcherPriority.ContextIdle);
}
private void Work2()
{
Dispatcher.Invoke(() =>
{
var text = Button1.Content;
var stopWatch = new Stopwatch();
stopWatch.Start();
for (var i = 0; i < 250000000; i++) { }
_t2 = stopWatch.Elapsed.ToString();
}, DispatcherPriority.ContextIdle);
}
}
}
【问题讨论】:
-
你正在调度一个线程池线程让它调度两个线程池线程,这两个线程都只是要求 UI 线程做一堆工作,让线程池线程等待它完成,并且然后让外部线程池线程等待 那些 完成。不要那样做。如果你想在 UI 线程中运行两件事,就在 UI 线程中运行它们,不要安排 3 个线程池线程坐在那里什么都不做,而你在 UI 线程中做 2 件事。
-
如果我明白你在说什么,我应该将 UI 工作放在 Dispatcher.Invoke() 方法中,并将计时器逻辑放在 Dispatcher.Invoke() 调用之外?
-
您根本不应该在后台工作中触摸 UI。如果有的话,你应该很少使用
Dispatcher.Invoke。从 UI 获取您需要的任何数据,然后创建一些工作人员并为他们提供数据,然后返回他们可能计算的任何结果,然后在后台工作完成后使用您的结果更新 UI。
标签: c# wpf parallel-processing async-await task-parallel-library