Добавление вызова AsParallel () приводит к сбою моего кода при записи файла - PullRequest
4 голосов
/ 09 декабря 2011

Я создаю консольное приложение, которое должно обрабатывать пачку документов.

Для простоты, процесс выглядит так:

  1. для каждого года между X и Y,запросить БД, чтобы получить список ссылок на документы для процесса
  2. для каждой из этих ссылок, обработать локальный файл

Метод процесса, я думаю, независим и должен быть распараллеленкак только входные аргументы различаются:

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 - это бизнес-класс, который переносит поискдокументы.Результатом этого метода является типизированный набор данных (я возвращаю таблицу документов).Метод ожидает год, и я уверен, что документ не может быть возвращен более чем на один год (на самом деле год является частью ключа).

Обратите внимание на использование 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 для рекомендаций

[Редактировать] Код для получения документа:

    /// <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 ]

3 голосов
/ 09 декабря 2011

Я предлагаю вам, чтобы каждая задача помещала данные файла в глобальную очередь, а параллельный поток принимал запросы на запись из очереди и выполнял фактическую запись.

В любом случае, производительность параллельной записи на одном диске намного хуже, чем последовательная запись, потому что диск должен вращаться, чтобы найти следующее место записи, поэтому вы просто отскакиваете диск между поисками. Лучше делать записи последовательно.

1 голос
/ 09 декабря 2011

Безопасен ли Document.FindAll((short)year).Documents поток? Поскольку разница между первой и второй версией заключается в том, что во второй (неработающей) версии этот вызов выполняется несколько раз одновременно. Вероятно, это может быть причиной проблемы.

0 голосов
/ 09 декабря 2011

Похоже, вы пытаетесь записать в тот же файл. Только один поток / программа может записывать в файл в данный момент времени, поэтому вы не можете использовать Parallel.

Если вы читаете из того же файла, то вам нужно открыть файл только с разрешениями на чтение, чтобы не устанавливать для него блокировку записи.

Самый простой способ решить эту проблему - установить блокировку вокруг вашего File.WriteAllText, предполагая, что запись выполняется быстро и стоит распараллеливать остальную часть кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...