如何在WPF中实现并行任务?
How to implement parallel task in WPF?
这只是我试图开始工作的示例测试代码。我知道有更好的方法来报告进度,但我只想知道为什么这段代码不起作用。我正在读取文件并报告进度,但它冻结了 UI,并等待整个任务完成。
private async void runtTasksButton_Click(object sender, RoutedEventArgs e)
{
List<Task> tasks = new List<Task>();
var t = Task.Factory.StartNew(() =>
{
FileStream fs = File.OpenRead(@"C:\Whatever.txt");
var totalLength = fs.Length;
this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar1.Maximum = totalLength;
}), null);
using (fs)
{
byte[] b = new byte[1024];
while (fs.Read(b, 0, b.Length) > 0)
{
this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar1.Value += 1024;
}), null);
};
}
});
tasks.Add(t);
await Task.WhenAll(tasks.ToArray());
}
编辑:我知道 BackgroundWorker
和 async/await
。但是,我只是想弄清楚这段代码,以便更深入地了解 TPL
。我确实使用了 FileStream.ReadAsync()
方法并且能够在运行时更新 ProgressBar
:
private void runtTasksButton_Click(object sender, RoutedEventArgs e)
{
progressTextBox.Text = "";
label1.Content = "Milliseconds: ";
progressBar1.Value = 0;
progressBar2.Value = 0;
var watch = Stopwatch.StartNew();
List<Task> tasks = new List<Task>();
var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1));
var t2 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile2.txt", progressBar2));
tasks.Add(t1);
tasks.Add(t2);
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
this.Dispatcher.BeginInvoke(new Action(() =>
label1.Content += time.ToString()));
});
}
private async Task ReadFile(string path, ProgressBar progressBar)
{
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 8192, true);
var totalLength = fs.Length;
await this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar.Maximum = totalLength;
}));
using (fs)
{
byte[] b = new byte[8192];
while (await fs.ReadAsync(b, 0, b.Length) > 0)
{
await this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar.Value += 8192;
}));
};
}
}
但是,现在控件进入 ContinueWhenAll
而无需等待文件读取任务完成。 但是任务会按预期更新相应的 ProgressBars。如果有人能给出一些解释,那将不胜感激。
这是Task.Factory.StartNew
的经典陷阱。方法 return 是 Task<T>
,其中 T
是回调的 return 类型。您的回调是 ReadFile
,其中 return 是一个任务。因此,你最终得到一个 Task<Task>
,你在等待外部任务,而你应该等待内部任务。
两种解决方案:
推荐的一种:使用Task.Run
代替Task.Factory.StartNew
。这是一般性建议,除非您知道这是正确的解决方案,否则永远不要使用 Task.Factory.StartNew
。 Task.Run
避免了很多陷阱,比如你 运行 进入的陷阱。
var t1 = Task.Run(() => ReadFile(@"C:\BigFile1.txt", progressBar1));
如果你有正当理由使用 Task.Factory.StartNew
,那么你可以在任务上使用 .Unwrap()
方法来等待内部任务:
var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1)).Unwrap();
这只是我试图开始工作的示例测试代码。我知道有更好的方法来报告进度,但我只想知道为什么这段代码不起作用。我正在读取文件并报告进度,但它冻结了 UI,并等待整个任务完成。
private async void runtTasksButton_Click(object sender, RoutedEventArgs e)
{
List<Task> tasks = new List<Task>();
var t = Task.Factory.StartNew(() =>
{
FileStream fs = File.OpenRead(@"C:\Whatever.txt");
var totalLength = fs.Length;
this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar1.Maximum = totalLength;
}), null);
using (fs)
{
byte[] b = new byte[1024];
while (fs.Read(b, 0, b.Length) > 0)
{
this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar1.Value += 1024;
}), null);
};
}
});
tasks.Add(t);
await Task.WhenAll(tasks.ToArray());
}
编辑:我知道 BackgroundWorker
和 async/await
。但是,我只是想弄清楚这段代码,以便更深入地了解 TPL
。我确实使用了 FileStream.ReadAsync()
方法并且能够在运行时更新 ProgressBar
:
private void runtTasksButton_Click(object sender, RoutedEventArgs e)
{
progressTextBox.Text = "";
label1.Content = "Milliseconds: ";
progressBar1.Value = 0;
progressBar2.Value = 0;
var watch = Stopwatch.StartNew();
List<Task> tasks = new List<Task>();
var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1));
var t2 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile2.txt", progressBar2));
tasks.Add(t1);
tasks.Add(t2);
Task.Factory.ContinueWhenAll(tasks.ToArray(),
result =>
{
var time = watch.ElapsedMilliseconds;
this.Dispatcher.BeginInvoke(new Action(() =>
label1.Content += time.ToString()));
});
}
private async Task ReadFile(string path, ProgressBar progressBar)
{
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None, 8192, true);
var totalLength = fs.Length;
await this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar.Maximum = totalLength;
}));
using (fs)
{
byte[] b = new byte[8192];
while (await fs.ReadAsync(b, 0, b.Length) > 0)
{
await this.Dispatcher.BeginInvoke(new Action(() =>
{
progressBar.Value += 8192;
}));
};
}
}
但是,现在控件进入 ContinueWhenAll
而无需等待文件读取任务完成。 但是任务会按预期更新相应的 ProgressBars。如果有人能给出一些解释,那将不胜感激。
这是Task.Factory.StartNew
的经典陷阱。方法 return 是 Task<T>
,其中 T
是回调的 return 类型。您的回调是 ReadFile
,其中 return 是一个任务。因此,你最终得到一个 Task<Task>
,你在等待外部任务,而你应该等待内部任务。
两种解决方案:
推荐的一种:使用
Task.Run
代替Task.Factory.StartNew
。这是一般性建议,除非您知道这是正确的解决方案,否则永远不要使用Task.Factory.StartNew
。Task.Run
避免了很多陷阱,比如你 运行 进入的陷阱。var t1 = Task.Run(() => ReadFile(@"C:\BigFile1.txt", progressBar1));
如果你有正当理由使用
Task.Factory.StartNew
,那么你可以在任务上使用.Unwrap()
方法来等待内部任务:var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1)).Unwrap();