【问题标题】:Ordering a ConcurrentDictionary. Why is this not working?订购并发字典。为什么这不起作用?
【发布时间】:2015-09-16 00:33:44
【问题描述】:

我们有一个 C# 应用程序,可以在 Excel 文档中的工作表上填充表格。

必须按照从数据库返回行的顺序填充表。

对象 DataFileColData 被定义为一个 List 并包含结果集行。出于测试目的,我只使用列表中的 [0]。

下面的代码段 #1 不起作用。尽管数字本身是按顺序列出的,但最终结果中的数据显示乱序,因此不会保留行顺序:

if (DataFileColData[0].Count() > 0)
{
    ConcurrentDictionary<int, DataRow> theRows = new ConcurrentDictionary<int, DataRow>(9, DataFileColData[0].Count());

    Parallel.For(0, DataFileColData[0].Count(), i =>
    {
        // go through each column
        int c = 0;
        try
        {
            foreach (var Col in DataFileColData)
            {
                var cell = Col[i];
                if (cell != null)
                {
                    if (cell.GetType().Name == "JArray") //If Jarray then table compression was used not column compression
                    {
                        if (theRows.TryAdd(i, Dt.NewRow()))
                            theRows[i].ItemArray = JsonConvert.DeserializeObject<object[]>(Col[i].ToString());
                    }
                    else
                    {
                        if (theRows.TryAdd(i, Dt.NewRow()))
                            theRows[i][c] = cell;
                    }
                }
                c++;
            }
        } //try
        catch (Exception e)
        {
            throw new Exception("Exception thrown in \"PublicMethods.cs | RenderExcelFile\" while in foreach loop over DataFileColData: " + e.ToString());
        }

    } //for
    ); //parallel

    //Add the rows to the datatable in their original order
    //(might have gotten skewed from the parallel.for loop)
    for (int x = 0; x < theRows.Count; x++)
        Dt.Rows.Add(theRows[x]);

    //Set the name so it appears nicely in the Excel Name Box dropdown instead of "table1", "table2", etc etc.
    Dt.TableName = ExcelTableSpec.TableTitle + " " + r.TableID;
}

下面的代码段 #2 确实适用于与保留的每一行关联的行顺序和数据:

if (DataFileColData[0].Count() > 0)
{
    DataRow[] theRows = new DataRow[DataFileColData[0].Count()];

    Parallel.For(0, DataFileColData[0].Count(), i =>
    {
        DataRow Rw = Dt.NewRow();

        // go through each column
        int c = 0;
        try
        {
            foreach (var Col in DataFileColData)
            {
                var cell = Col[i];
                if (cell != null)
                {
                    if (cell.GetType().Name == "JArray") //If Jarray then table compression was used not column compression
                    {
                        lock (theRows)
                        {
                            theRows[i] = Dt.NewRow();
                            theRows[i].ItemArray = JsonConvert.DeserializeObject<object[]>(Col[i].ToString());
                        }
                    }
                    else
                    {
                        lock (theRows)
                        {
                            theRows[i] = Dt.NewRow();
                            theRows[i][c] = cell;
                        }
                    }
                }
                c++;
            }
        } //try
        catch (Exception e)
        {
            throw new Exception("Exception thrown in \"PublicMethods.cs | RenderExcelFile\" while in foreach loop over DataFileColData: " + e.ToString());
        }

    } //for
    ); //parallel

    //Add the rows to the datatable in their original order
    //(might have gotten skewed from the parallel.for loop)
    Dt = theRows.CopyToDataTable();

    //Set the name so it appears nicely in the Excel Name Box dropdown instead of "table1", "table2", etc etc.
    Dt.TableName = ExcelTableSpec.TableTitle + " " + r.TableID;
}

我不明白为什么。我认为不需要锁定机制,因为每个线程都有自己的“i”实例,并且 ConcurrentDictionary 应该是线程安全的。

有人能向我解释一下为什么代码没有按照我认为的方式工作吗?

谢谢!


根据@Enigmativity 下面的 cmets 更新代码。

MSDN 文档不是很清楚(无论如何对我来说),但似乎确实更新了 DataTable,即使 MSDN 文档没有表明它在执行 NewRow() 方法时会这样做。

下面的新工作代码:

if (DataFileColData[0].Count() > 0)
                {
                    DataRow[] theRows = new DataRow[DataFileColData[0].Count()];

                    Parallel.For(0, DataFileColData[0].Count(), i =>
                    //for (int i = 0; i < DataFileColData[0].Count(); i++)
                    {
                        lock (Dt)
                        {
                            theRows[i] = Dt.NewRow();
                        }

                        // go through each column
                        int c = 0;
                        try
                        {
                            foreach (var Col in DataFileColData)
                            {
                                var cell = Col[i];
                                if (cell != null)
                                {
                                    if (cell.GetType().Name == "JArray") //If Jarray then table compression was used not column compression
                                    {
                                        theRows[i].ItemArray = JsonConvert.DeserializeObject<object[]>(Col[i].ToString());
                                    }
                                    else
                                    {
                                        theRows[i][c] = cell;
                                    }
                                }
                                c += 1;
                            } //foreach
                        } //try
                        catch (Exception e)
                        {
                            throw new Exception("Exception thrown in \"PublicMethods.cs | RenderExcelFile\" while in foreach loop over DataFileColData: " + e.ToString());
                        }

                    } //for
                    ); //parallel

                    //Add the rows to the datatable in their original order
                    //(might have gotten skewed from the parallel.for loop)
                    Dt = theRows.CopyToDataTable();

                    //Set the name so it appears nicely in the Excel Name Box dropdown instead of "table1", "table2", etc etc.
                    Dt.TableName = ExcelTableSpec.TableTitle + " " + r.TableID;

                    //cleanup
                    if (theRows != null)
                        Array.Clear(theRows, 0, theRows.Length);
                    theRows = null;

                } //if (DataFileColData[0].Count() > 0)

【问题讨论】:

    标签: c# concurrency parallel-processing concurrentdictionary parallel.for


    【解决方案1】:

    请参阅 (MSDN Data Tables) 的文档。

    重点是:

    线程安全

    这种类型对于多线程读取操作是安全的。你必须 同步任何写操作。

    所以不是iConcurrentDictionary 导致您的问题。


    我已经反编译了NewRow 方法并且有一个对NewRow(int record) 的调用。这段代码清楚地显示了写操作。

    internal DataRow NewRow(int record)
    {
      if (-1 == record)
        record = this.NewRecord(-1);
      this.rowBuilder._record = record;
      DataRow row = this.NewRowFromBuilder(this.rowBuilder);
      this.recordManager[record] = row;
      if (this.dataSet != null)
        this.DataSet.OnDataRowCreated(row);
      return row;
    }
    

    【讨论】:

    • 我很困惑,因为直到 Parallel.For 循环之外我才真正添加到 DataTable;只有新的 DataRows 被创建并添加到带有 DataTable 架构的 ConcurrentDictionary 中,这不会改变。当我向 DataTable 添加行时,它处于单线程 for 循环中,因此不应该存在任何线程问题。
    • @FreeCoder24 - 我接听了Dt.NewRow(); 的电话是问题所在。它们在For.Parallel 中,它们是写操作。
    • 查看MSDN,它说,“您必须使用NewRow 方法创建与DataTable 具有相同架构的新DataRow 对象。创建DataRow 后,您可以将其添加到DataRowCollection,通过 DataTable 对象的 Rows 属性”,这意味着它不会更改 DataTable。然后将新数据行对象添加到 ConcurrentDictionary 中的特定键,并且仅添加该特定键 (I)。因此,I=2 的线程不应更改 I=109 的线程上的数据行。这不是真的吗??
    • @FreeCoder24 - 它可能不会改变 DataTime,但它显然是在做内部分配 - this.rowBuilder._record = record; && this.recordManager[record] = row;。我不相信这个操作是线程安全的。
    • 整个过程需要几分钟,具体取决于返回的行数和列数。当有几千条记录时,这个循环是很长的时间:大约 25 秒。通过并行,这部分现在减少到大约 15 秒。我将把它标记为答案,因为我已经重写了考虑到您的 cmets 的代码,尽管 MSDN 说了什么,但它似乎是正确的。感谢您花时间对 NewRow 方法进行逆向工程以查看内部结构。
    猜你喜欢
    • 2010-10-06
    • 2019-10-06
    • 1970-01-01
    • 1970-01-01
    • 2014-01-03
    • 1970-01-01
    • 2011-11-07
    • 2012-03-06
    相关资源
    最近更新 更多