【问题标题】:How to implement parallel task in WPF?如何在 WPF 中实现并行任务?
【发布时间】:2018-05-30 20:45:57
【问题描述】:

这只是我试图开始工作的示例测试代码。我知道有更好的方法来报告进度,但我只想知道为什么这段代码不起作用。我正在读取文件并报告进度,但它会冻结 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());
}

编辑:我知道BackgroundWorkerasync/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,无需等待文件读取任务完成。但任务确实按预期更新了相应的进度条。如果有人能给出一些解释,那将不胜感激。

【问题讨论】:

  • 如果您正确使用async/await,则通常不需要Dispatcher.BeginInvoke,因为当任务完成时,控制权会返回到原始线程,在这种情况下是UI线程。这里的问题是您正在创建另一个受 CPU 限制的 Task (Task.Factory.StartNew),它很可能是一个工作线程。不要将 CPU 密集型任务与 I/O 密集型操作(例如读取文件)一起使用。首先使用xxxAsync 版本的操作。不需要Task.Run 或等效项
  • MickyD,请阅读我编辑的问题。 CPU 密集型任务和 xxxAsync 版本的操作有什么区别?

标签: c# wpf async-await task-parallel-library


【解决方案1】:

这是Task.Factory.StartNew 的经典陷阱。该方法返回一个Task&lt;T&gt;,其中T 是回调的返回类型。您的回调是ReadFile,它返回一个任务。因此,您最终得到一个Task&lt;Task&gt;,并且您正在等待外部任务,而您应该等待内部任务。

两种解决方案:

  1. 推荐的一种:使用Task.Run 而不是Task.Factory.StartNew。这是一般性建议,除非您知道这是正确的解决方案,否则您永远不应该使用 Task.Factory.StartNewTask.Run 避免了很多陷阱,比如你遇到的那个。

    var t1 = Task.Run(() => ReadFile(@"C:\BigFile1.txt", progressBar1));
    
  2. 如果您有正当理由使用Task.Factory.StartNew,那么您可以在任务上使用.Unwrap() 方法以便能够在内部等待:

    var t1 = Task.Factory.StartNew(() => ReadFile(@"C:\BigFile1.txt", progressBar1)).Unwrap();
    

【讨论】:

  • 非常感谢,这正是问题所在!
猜你喜欢
  • 1970-01-01
  • 2017-06-28
  • 2015-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-11
  • 1970-01-01
相关资源
最近更新 更多