我试图在运行
Parallel.Foreach
时更新进度条,但在执行过程中没有任何反应。仅当 For 循环结束时进度条才会更新。我怎样才能让这段代码工作?
XAML
<StackPanel>
<Grid x:Name="LoadProgressGrid" Height="100"
Visibility="Visible">
<ProgressBar x:Name="LoadProgress"
Maximum="100"
Minimum="1" />
<TextBlock Margin="0,0,0,-5"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="16"><Run Text="Import Progress"/></TextBlock>
</Grid>
<Button Height="35" Width="100" Margin="0,10" Content="Test" Click="Test_Click"/>
</StackPanel>
C#
private void Test_Click(object sender, RoutedEventArgs e)
{
decimal current=0;
List<string> lst = new List<string>();
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
decimal max = 100000;
var uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
Parallel.ForEach(lst, (data) =>
{
for (int i = 0; i < max; i++)
{
// Use the uiFactory above:
// Note the need for a temporary here to avoid closure issues!
current = current + 1;
uiFactory.StartNew( () => LoadProgress.Value = (double)(current/max)*100);
}
});
MessageBox.Show("Done!");
}
正如评论中指出的,
Parallel.ForEach
在循环完成之前不会返回,这将阻塞它运行的线程,并且Servy在您的回答中的评论还说您正在从各个线程访问和更改状态而无需同步或对象锁定(请参阅竞争条件)。
如果您不确定更改 UI 线程状态的正确方法,那么您可以让框架使用
IProgress<T>
来完成捕获上下文的工作。
关于您的答案,您可以将
async
关键字直接放在 Test_Click
事件处理程序上,同时保留返回类型 void
,但请记住,不建议或建议对任何其他 async
方法使用 void ,并且他们应该返回 Task
或 Task<T>
类型,在此处阅读更多内容,了解为什么 async void
是一个坏主意。
更重要的是,这里有一个使用
async
和非阻塞代码来报告进度并更新进度栏的代码片段,我已经对代码进行了注释以使其更具可读性。
// Add the async keyword to our event handler
private async void Button_Click(object sender, RoutedEventArgs e)
{
//Begin our Task
Task downloadTask = DownloadFile();
// Await the task
await downloadTask;
}
private async Task DownloadFile()
{
// Capture the UI context to update our ProgressBar on the UI thread
IProgress<int> progress = new Progress<int>(i => { LoadProgress.Value = i; });
// Run our loop
for (int i = 0; i < 100; i++)
{
int localClosure = i;
// Simulate work
await Task.Delay(1000);
// Report our progress
progress.Report((int)((double)localClosure / 100 * 100));
}
}
从这个答案:Using Task with Parallel.Foreach in .NET 4.0和Servy的评论我让它工作了。
private void Test_Click(object sender, RoutedEventArgs e)
{
test();
}
public async void test()
{
decimal current = 0;
List<string> lst = new List<string>();
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
lst.Add("Foo");
decimal max = 10000;
//Remove await (and async from signature) if, want to see the message box rightway.
await Task.Run(() => Parallel.ForEach(lst, (data) =>
{
for (int i = 0; i < max; i++)
{
current = current + 1;
Dispatcher.Invoke(new Action(() => LoadProgress.Value = (double)(current / max) * 100));
}
}));
MessageBox.Show("Done!");
}