C# WPF中的并行任务
我试图在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
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线程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();
}