【问题标题】:Parallel.ForEach nothing or very little is happeningParallel.ForEach 没有或很少发生
【发布时间】:2018-07-09 13:44:01
【问题描述】:

我正在尝试读取 excel 文档并将其写为 csv。

我已经想出了如何以几种略有不同的方式来做到这一点,但它的速度非常慢。

这就是我所拥有的,它正在运行 2 个方舟,每个 16384 行和 5 列数据在大约 1 分 36 秒内运行

  public void ToCSV(Stream excelStream, int i)
    {
        // IExcelDataReader excelReader = null;

        using (var excelReader = ExcelReaderFactory.CreateReader(excelStream))
        {
            System.Data.DataSet excelsheets = excelReader.AsDataSet();

            foreach (DataTable aSheet in excelsheets.Tables)
            {
                string strCSVData = "";
                string sheetName = aSheet.TableName;

                foreach (DataRow row in aSheet.Rows)
                {
                    foreach (var column in row.ItemArray)
                    {
                        strCSVData += column.ToString().Replace(",", ",") + ",";
                    }
                    strCSVData += "\n";
                }
                string strOutputFileName = Directory.GetCurrentDirectory() + sheetName + i.ToString() + ".csv";
                StreamWriter csvFile = new StreamWriter(strOutputFileName, false);
                csvFile.Write(strCSVData);
                csvFile.Close();
            }
        }
    }

现在我正在尝试加快速度。我使用普通的 for 循环稍微快了一点,但在大约 1 分 33 秒时没有什么特别的。

所以我想改用 Parallel.foreach 怎么样。然而,这导致要么只有三分之一的数据被写入,要么目前没有。

这就是我改变上述方法的方式。

 public void ToCSVParallel(Stream excelStream, int i)
    {
        // IExcelDataReader excelReader = null;

        using (var excelReader = ExcelReaderFactory.CreateReader(excelStream))
        {
            System.Data.DataSet excelsheets = excelReader.AsDataSet();

            for (int sheet = 0; sheet < excelsheets.Tables.Count; sheet++)
            {
                DataTable aSheet = excelsheets.Tables[sheet];
                List<string> strCSVData = new List<string>();
                string sheetName = aSheet.TableName;
                IEnumerable<DataRow> dataSheet = aSheet.AsEnumerable();
                Parallel.ForEach<DataRow>(dataSheet, row =>
                {
                    string strRow = "";
                    for (int column = 0; column < row.ItemArray.Count(); column++)
                    {
                        strRow = row[column].ToString().Replace(",", "&comma;") + ",";
                    }
                    strRow += "\n";
                    strCSVData.Append(strRow);
                });

                string strOutputFileName = Directory.GetCurrentDirectory() + sheetName + i.ToString() + ".csv";
                //StreamWriter csvFile = new StreamWriter(strOutputFileName, false);
                System.IO.File.WriteAllLines(strOutputFileName, strCSVData);
              //  csvFile.Write(strCSVData);
                //csvFile.Close();
            }
        }
    }

现在我不知道我做错了什么但我很确定我一定是误解了我如何使用 parallel.foreach 但我做错了什么?

或者是否有更好/更智能/更简单的方法来加快我的方法?

编辑:

根据您的所有建议,我提出了以下更改。

public void ToCSVParallel(Stream excelStream, int i)
    {
        using (var excelReader = ExcelReaderFactory.CreateReader(excelStream))
        {
            System.Data.DataSet excelsheets = excelReader.AsDataSet();

            for (int sheet = 0; sheet < excelsheets.Tables.Count; sheet++)
            {
                DataTable aSheet = excelsheets.Tables[sheet];
                ConcurrentBag<string> strCSVData = new ConcurrentBag<string>();
                string sheetName = aSheet.TableName;
                IEnumerable<DataRow> dataSheet = aSheet.AsEnumerable();
                Parallel.ForEach<DataRow>(dataSheet, row =>
                {
                    StringBuilder strRow = new StringBuilder();
                    for (int column = 0; column < row.ItemArray.Count(); column++)
                    {
                        strRow.Append(row[column].ToString().Replace(",", "&comma;") + ",");
                    }
                    strCSVData.Add(strRow.ToString());
                });

                string strOutputFileName = Directory.GetCurrentDirectory() + sheetName + i.ToString() + ".csv";
                System.IO.File.WriteAllLines(strOutputFileName, strCSVData);
            }
        }
    }

但是根据@Magnus 的建议,我也将原来的方法更改为:

public void ToCSV(Stream excelStream, int i)
    {
        using (var excelReader = ExcelReaderFactory.CreateReader(excelStream))
        {
            System.Data.DataSet excelsheets = excelReader.AsDataSet();
            foreach (DataTable aSheet in excelsheets.Tables)
            {
                string sheetName = aSheet.TableName;
                string strOutputFileName = Directory.GetCurrentDirectory() + sheetName + i.ToString() + ".csv";

                using (StreamWriter csvFile = new StreamWriter(strOutputFileName, false))
                {
                    foreach (DataRow row in aSheet.Rows)
                    {
                        foreach (var column in row.ItemArray)
                        {
                            csvFile.Write(column.ToString().Replace(",", "&comma;") + ",");
                        }
                        csvFile.WriteLine();
                    }
                }
            }
        }
    }

结果令我惊讶。

并行比修改后的 Foreach 循环平均慢 1000 毫秒。

但是,我让该方法更快的想法现在在可接受的范围内。 并行平均需要大约 8800 毫秒。 foreach 循环平均需要 7600 毫秒。 这两个都在 2 个方舟上,每个 16384 行和 5 列数据

【问题讨论】:

  • 看着我对循环中的 strCSVData.Append 感到紧张。我不认为这是一个线程安全的结构,我也不认为在这里使用它是有意义的。也许返回您添加的值,然后组合 ParallelLoopResults?虽然我猜你真正想要的是每个线程一个列表,添加到该列表,然后将它们组合起来。
  • 使用 Excel 的功能导出为 CSV 会不会简单很多?
  • 由于您使用的是异步操作,因此可能会同时发生许多写入,并且在写入过程中,工作表被锁定并丢失了一些写入调用。最好的办法是查询使用 excel 驱动程序并使用该查询转储所有行。
  • 请注意 Parallel.ForEach 可以按任意顺序启动任务。另请注意,任务完成的顺序也是任意的(不一定是任务开始的顺序)您的代码现在按照任务完成的顺序将数据添加到 strCSVData。但是您应该按照各个 Excel 数据行的顺序将 strRow 数据添加到 strCSVData。此外,可能会发生两个任务同时附加到 strCSVData,本质上会破坏附加的数据......
  • 为了清楚起见,在不尝试运行 Replace 或将其写入 CSV 的情况下读取 excel 文档需要多长时间?我认为在我们解决问题之前可以划分优化。

标签: c# .net parallel.foreach


【解决方案1】:

您的代码存在一些问题。

  1. strCSVData.Append(strRow) 实际上并没有向列表中添加任何内容,它返回一个新的枚举并附加了项目。
  2. 如果你真的做了Add 那也不会工作,因为 List 不是线程安全的。
  3. 并行处理不会按顺序处理项目。 (必须)

我认为您的原始(非并行循环)的主要问题是通过连接构建strCSVData。由于字符串是不可变的,因此每次都必须创建一个新字符串,并且它越大越慢。我建议您在循环之前打开StreamWriter,然后直接写入那个。

...
StreamWriter csvFile = new StreamWriter(strOutputFileName, false);
for (int sheet = 0; sheet < excelsheets.Tables.Count; sheet++)
{
...
  foreach (DataRow row in aSheet.Rows)
  {
      foreach (var column in row.ItemArray)
      {
           csvFile.WriteLine(column.ToString().Replace(",", "&comma;") + ",");
      }
  }
...
}

【讨论】:

  • 这是一个有趣的方法,我一定会尝试的。你说我的列表不是线程安全的。你会建议什么而不是 list ?我可以直接使用相同的写作方法吗?还是我会冒着每行同时写入同一行的风险?
  • 您建议对原始方法的更改已使运行时间发生了令人印象深刻的变化。现在我们计算的是毫秒而不是分钟。几乎认为我不需要做并行。但是我不关心每一行的顺序,只关心与每一行匹配的所有数据是逐行相关的。如果我能获得一点点性能,那么我相信并行会更快。
  • 我将在今天结束之前将您的答案添加为已接受的答案,除非有人提出一种革命性更快的方法,那么您的答案就是最佳答案。
  • 并行执行并不总是更快,因为它通常会增加处理同步的复杂性。
猜你喜欢
  • 2017-10-13
  • 1970-01-01
  • 1970-01-01
  • 2014-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-26
  • 1970-01-01
相关资源
最近更新 更多