Parallel.ForEach几乎什么也没有发生

时间:2018-07-09 13:44:02

标签: c# .net parallel.foreach

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

我已经找到了如何以几种略有不同的方式来执行此操作,但是速度慢得要命。

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

  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列数据

1 个答案:

答案 0 :(得分:2)

与您的代码有关的几个问题。

  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;") + ",");
      }
  }
...
}