添加AsParallel()调用会导致我的代码在写入文件时中断

时间:2011-12-09 16:40:39

标签: c# parallel-processing

我正在构建一个必须处理大量文档的控制台应用程序。

为了保持简单,过程是:

  1. 对于X和Y之间的每年,查询数据库以获取要处理的文档引用列表
  2. 对于每个参考,处理本地文件
  3. 我认为,流程方法是独立的,只要输入参数不同,就应该并行化:

    private static bool ProcessDocument(
            DocumentsDataset.DocumentsRow d,
            string langCode
    )
    {         
            try
            {                           
                var htmFileName = d.UniqueDocRef.Trim() + langCode + ".htm";
                var htmFullPath = Path.Combine("x:\path", htmFileName;
    
                missingHtmlFile = !File.Exists(htmFullPath);
    
                if (!missingHtmlFile)
                {
                    var html = File.ReadAllText(htmFullPath);
    
                    // ProcessHtml is quite long : it use a regex search for a list of reference
                    // which are other documents, then sends the result to a custom WS
    
                    ProcessHtml(ref html);
    
                    File.WriteAllText(htmFullPath, html);
    
                }
    
                return true;             
            }
            catch (Exception exc)
            {
                Trace.TraceError("{0,8}Fail processing {1} : {2}","[FATAL]", d.UniqueDocRef, exc.ToString());
                return false;
            }
        }
    

    为了枚举我的文档,我有这个方法:

        private static IEnumerable<DocumentsDataset.DocumentsRow> EnumerateDocuments()
        {           
            return Enumerable.Range(1990, 2020 - 1990).AsParallel().SelectMany(year => {
                return Document.FindAll((short)year).Documents;
            });
        }
    

    Document是一个包装文档检索的业务类。此方法的输出是一个类型化数据集(我正在返回Documents表)。该方法等待了一年,我确信一份文件不能超过一年(实际上是年份的一部分)。

    请注意AsParallel()在这里使用,但我从来没有遇到过这个问题。

    现在,我的主要方法是:

            var documents = EnumerateDocuments();
    
            var result = documents.Select(d => {
                bool success = true;
                foreach (var langCode in new string[] { "-e","-f" })
                {
                    success &= ProcessDocument(d, langCode);
                }
                return new { 
                    d.UniqueDocRef,
                    success
                };
            });
            using (var sw = File.CreateText("summary.csv"))
            {
                sw.WriteLine("Level;UniqueDocRef");
                foreach (var item in result)
                {
                    string level;
                    if (!item.success) level = "[ERROR]";
                    else level = "[OK]";
    
                    sw.WriteLine(
                        "{0};{1}",
                        level,
                        item.UniqueDocRef
                        );
                    //sw.WriteLine(item);
                }
            }
    

    此方法在此表单下按预期工作。但是,如果我替换

            var documents = EnumerateDocuments();
    

    通过

            var documents = EnumerateDocuments().AsParrallel();
    

    它停止工作,我不明白为什么。

    错误正好出现在这里(在我的处理方法中):

    File.WriteAllText(htmFullPath, html);
    

    它告诉我该文件已被其他程序打开。

    我不明白是什么原因导致我的程序不按预期工作。由于我的documents变量是IEnumerable返回的唯一值,为什么我的流程方法会破坏?

    thx for advises

    [编辑] 检索文档的代码:

        /// <summary>
        /// Get all documents in data store
        /// </summary>
        public static DocumentsDS FindAll(short? year)
        {
            Database db = DatabaseFactory.CreateDatabase(connStringName); // MS Entlib
    
            DbCommand cm = db.GetStoredProcCommand("Document_Select");
            if (year.HasValue) db.AddInParameter(cm, "Year", DbType.Int16, year.Value);
    
            string[] tableNames = { "Documents", "Years" };
            DocumentsDS ds = new DocumentsDS();
            db.LoadDataSet(cm, ds, tableNames);
    
            return ds;
        }
    

    [Edit2] 我的问题的可能来源,感谢mquander。如果我写道:

            var test = EnumerateDocuments().AsParallel().Select(d => d.UniqueDocRef);
    
            var testGr = test.GroupBy(d => d).Select(d => new { d.Key, Count = d.Count() }).Where(c=>c.Count>1);
    
            var testLst = testGr.ToList();
    
            Console.WriteLine(testLst.Where(x => x.Count == 1).Count());
            Console.WriteLine(testLst.Where(x => x.Count > 1).Count());
    

    我得到了这个结果:

    0
    1758
    

    删除AsParallel会返回相同的输出。

    结论:我的EnumerateDocuments有问题,每个文档返回两次。

    不得不潜入这里我想

    这可能是我在原因中的源枚举

3 个答案:

答案 0 :(得分:3)

我建议你让每个任务将文件数据放入一个全局队列,并让一个并行线程从队列中写入请求并进行实际写入。

无论如何,在单个磁盘上并行写入的性能比顺序写入要差得多,因为磁盘需要旋转以寻找下一个写入位置,因此您只是在搜索之间弹出磁盘。最好按顺序执行写操作。

答案 1 :(得分:1)

Document.FindAll((short)year).Documents线程安全吗?因为第一个和第二个版本之间的区别在于第二个(损坏)版本,所以此调用同时运行多次。这可能是问题的原因。

答案 2 :(得分:-1)

听起来你正在尝试写入同一个文件。只有一个线程/程序可以在给定时间写入文件,因此您不能使用Parallel。

如果您正在读取同一个文件,那么您需要打开只具有读取权限的文件,而不是对其进行写锁定。

解决问题的最简单方法是在File.WriteAllText周围设置一个锁定,假设编写速度很快,并且值得并行化其余代码。