【问题标题】:Reporting progress from Async Task从异步任务报告进度
【发布时间】:2017-02-01 00:31:43
【问题描述】:

各位开发者您好,

我面临着一个小小的困境。我有一个 WPF 应用程序,它读取一个相当大的 Excel 文件,然后将其输出为 XML 文件。我面临的问题是我想将操作的进度报告给处理程序。

我无法让它“顺利”工作,实际上,GUI 线程中的进度条会立即被填满,然后保存从 Excel 文件读取的内容的 DataGrid 会立即被填满。

我是 Async/Await/Task 的新手,直到现在我一直在使用 BackgroundWorker,但我希望获得更多关于这方面的知识,所以如果这是一个愚蠢的问题,我很抱歉。

我已经阅读了here.Stephen Cleary 的教程

老实说,我不知道为什么进度条没有按应有的“平滑”填充...

在 GUI 中被调用的代码:

        var progress = new Progress<int>(progressPercent => pBar.Value = progressPercent);

        Task.Run(() => _sourceService.ReadFileContent(filePath, progress)).ContinueWith(task =>
        {

            dataGrid.ItemsSource = task.Result.DefaultView;
            DataTable = task.Result;

            if (DataTable.Rows.Count > 0)
                BtnCreateXmlFile.IsEnabled = true;

        }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

ReadFileContent(filePath, progress)的方法体:

public DataTable ReadFileContent(string filePath, IProgress<int> progress)
    {
        var rowStart = 7;
        var columnStart = 1;

        var existingFile = new FileInfo(filePath);
        using (var package = new ExcelPackage(existingFile))
        {
            var worksheet = package.Workbook.Worksheets["BOP"];
            var dt = new DataTable();

            // Compose the name of the table from:
            // - Product Type
            // - Customer
            // - Customer Project
            // * These can be found in the "CollaborationContext" sheet that is present in the ExcelFile.
            if (package.Workbook.Worksheets["CollaborationContext"].SelectedRange["B6"].Value.Equals("object_type"))
            {
                dt.TableName = package.Workbook.Worksheets["CollaborationContext"].SelectedRange["C9"].Value + " " +
                               package.Workbook.Worksheets["CollaborationContext"].SelectedRange["D9"].Value + " " +
                               package.Workbook.Worksheets["CollaborationContext"].SelectedRange["E9"].Value;
            }


                dt.TableName = package.Workbook.Worksheets["CollaborationContext"].SelectedRange["B9"].Value + " " + 
                               package.Workbook.Worksheets["CollaborationContext"].SelectedRange["C9"].Value + " " +
                               package.Workbook.Worksheets["CollaborationContext"].SelectedRange["D9"].Value;


            // Report only in chunks. We do not want to call `progress.Report` for each row that is read.
            var totalRows = worksheet.Dimension.End.Row;
            var currentIndex = 0;
            var percentageProgress = totalRows / 10;


            // Get Columns and add them to the DataTable
            for (var col = columnStart; col <= worksheet.Dimension.End.Column - 1; col++)
                dt.Columns.Add(worksheet.Cells[6, col].Value.ToString());

            // Place data into DataTable
            for (var row = rowStart; row <= worksheet.Dimension.End.Row; row++)
            {
                var dr = dt.NewRow();
                var x = 0;
                currentIndex++;

                for (var col = columnStart; col <= worksheet.Dimension.End.Column - 1; col++)
                {
                    dr[x++] = worksheet.Cells[row, col].Value;
                }
                dt.Rows.Add(dr);

                // Report progress back to the handler
                if (currentIndex % percentageProgress == 0)
                    progress?.Report(row);
            }

            return dt;
        } 

    }

提前谢谢你!

【问题讨论】:

  • Excel 文件有多少行?一旦您到达第 100 行,进度条就会显示为 100,因为这是您在 progress?.Report() 中报告的数字。或类似的东西。我真的不明白if (currentIndex % percentageProgress == 0),但也许我需要更仔细地阅读。
  • 您好,已读取 805 行。哦等等,我是否必须将进度条的最大值设置为高于总行数?!
  • 这取决于你。如果您不想这样做,可以报告百分比而不是行索引。
  • 您说的完全正确,我注意到我将row 传递给了 ReportProgress 方法。问题是,我如何找出百分比,我尝试使用:` if (currentIndex % percentProgress == 0) progress?.Report(row);`
  • 1) 你应该 awaiting 你正在创建的任务 2) 你应该避免 Task.Run() 当工作将是 I/O-bound 3) 你的进度 pBar.Value = progressPercent 委托未在 UI 线程上执行,因此不会更新

标签: c# wpf asynchronous task


【解决方案1】:

首先,让我们摆脱危险的ContinueWith 调用。你真的应该改用await:

var progress = new Progress<int>(progressPercent => pBar.Value = progressPercent);
var result = await Task.Run(() => _sourceService.ReadFileContent(filePath, progress));
dataGrid.ItemsSource = result.DefaultView;
DataTable = result;
if (DataTable.Rows.Count > 0)
  BtnCreateXmlFile.IsEnabled = true;

接下来,您可能会在进度中看到的问题是您的 Progress&lt;int&gt; 处理程序期待 progressPercent,但 ReadFileContent 发送的是它读取的行数,不是百分比.所以,要解决这个问题:

if (currentIndex % percentageProgress == 0)
  progress?.Report(row * 100 / totalRows);

(这里还有其他几个选项;例如,如果您想要更精美的 UI,您可以决定报告当前行和总行)。

进度条没有按应有的“平滑”填充

我上面描述的是最低可接受的解决方案。特别是“只更新10次”的代码有点问题;它总是更新 10 次,无论更新速度有多快或多慢。更通用的解决方案是使用基于 Rx 的 IProgress 解决方案,这将允许您根据时间进行节流(例如,每秒 4 次更新)。

【讨论】:

  • 标记为答案。我已经修改了您发布的代码,现在可以使用。我还修改了 progress?.Report() 方法,因此它将百分比返回到 UI 线程中的进度条。问题是我真的需要阅读你关于 Async / Await / Task.Run 等的博客文章。你有一个可以遍历所有文章的起点吗?
  • @darksleep:不是所有的,不。对于async/await,我建议从here开始,对于Task.Run,我建议从here开始。
  • 还有一个问题。我明白,如果我调用 Task.Run(()=>DoSomething()) 线程池中的一个线程将被请求完成这项工作。那么,如果我有一个与上面类似的方法,并且我用异步标记事件处理程序 - Button1_Click() 并在主体内调用await ReadContentFile(),那么有什么区别。没有Task.Run(),什么都没有。
  • @darksleep:假设您也将 ReadContentFile 更改为异步方法,那将可以正常工作。
  • 这意味着我必须将ReadContentFile 标记为async 并以某种方式将长时间运行的操作标记为await。在这种特殊情况下,我不会用await 标记什么,因为基本上整个方法是for-loop,它读取Excel 文件的内容?换句话说,如果我在方法上调用await,并且方法不会被标记为async,那么基本上它会同步运行?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多