Вопрос или проблема
Чтобы получить более быстрое создание файлов из SSRS, я создал код Parallel.ForEachAsync, чтобы распределить выполнение по 4 потокам, затем: выполнить несколько запросов к базе данных, за которыми следуют запросы SSRS для генерации отчетов и, наконец, записать байты результата на жесткий диск. Но по какой-то причине имя файла не соответствует содержимому созданного файла. Имя файла формируется на основе некоторых значений из тех же параметров, которые использовались для запроса генерации отчета в SSRS.
Первое любопытное обстоятельство заключается в том, что при генерации единственного файла все проходит хорошо и имя файла соответствует содержимому соответствующего файла, что заставляет меня думать, что это может быть связано с проблемами, вызванными параллельными потоками.
Я включаю только набросок с основными строками кода для создания файла, используя Parallel.ForEachAsync
по причинам простоты:
public async Task ProcessReportOnParallelAsync( PocoClass pocoObj )
{
List<People> people = (await this.PeopleRepository.RetrieveAllInvolved( pocoObj.Params )).ToList();
await Parallel.ForEachAsync( people, new ParallelOptions{ MaxDegreeOfParallelism = 4 }, async ( personPoco, token ) =>
{
var personDetail = await this.PersonDetailsRepository( personPoco.Id, personPoco.CompanyId );
if( personDetail.ShouldGenerateReport )
{
await Report.GenerateAsync( personPoco );
}
});
}
public async static Task GenerateAsync( PocoClass pocoObj )
{
// настройка всей конфигурации объектов SSRS для запроса генерации отчета
var rsExec = new ReportExecutionServiceSoapClient( httpBinding, new EndpointAddress(SSRSReportExecutionUrl));
// ...
// В конце
await rsExec.SetExecutionParameterAsync( null, null, reportParams, "en-us" );
const string deviceInfo = @"<DeviceInfo><Toolbar>False</Toolbar><DeviceInfo>";
var response = await rsExec.RenderAsync( new RenderRequest(null, null, "EXCELOPENXML", deviceInfo));
var byteResults = response.Result;
string filename = $@"{pocoObj.Id}_{pocoObj.Name}_{pocoObj.Company}";
string filepath = $@"{this.FolderPathForFileGeneration}\{filename}";
var fs = new FileStream(filepath, FileMode.Create, FileAccess.ReadWrite);
await fs.WriteAsync( byteResults);
fs.Close();
}
Второе любопытное обстоятельство заключается в том, что я ранее создал очень похожий код (который послужил базой для этого) для другого контекста/отчета SSRS в том же проекте. Единственное отличие (исключая параметры и классы) в инструкциях Parallel и SSRS – это параметр RenderRequest, вместо RenderRequest(null, null, "EXCELOPENXML", deviceInfo)
другой генерирует PDF: RenderRequest(null, null, "PDF", deviceInfo)
, в этом случае все проходит хорошо, имена файлов соответствуют их содержимому.
Поэтому я хотел бы понять, что может происходить, что я не могу выяснить: хранимая процедура, связанная с генерацией отчета, или разница в конфигурации между двумя отчетами SSRS, или что-то другое, я не знаю.
Также, если есть решение или другое предложение или лучший подход к этой реализации.
Заранее спасибо!
Ответ или решение
Проблема несоответствия имен файлов и содержимого при создании отчетов SSRS в C# .NET 8
Ваша проблема с несоответствием имен файлов и их содержимого при использовании Parallel.ForEachAsync
для генерации отчетов SSRS может быть связана с несколькими факторами. Давайте подробно разберем возможные причины и предложим подходы для их устранения.
1. Конкуренция потоков
При запуске нескольких потоков параллельно (в данном случае 4) существует вероятность, что доступ к общим ресурсам может происходить одновременно. В вашем коде процесс создания имени файла (переменная filename
) зависит от параметров, которые обрабатываются в каждом потоке. Если вы используете состояние, изменяемое во время выполнения, это может вызвать условие гонки, при котором потоки могут генерировать имя файла до того, как отчет будет фактически сгенерирован.
Рекомендация: Создайте отдельную локальную переменную для каждого потока, содержащую полное имя файла, во избежание путаницы:
var filename = $@"{pocoObj.Id}_{pocoObj.Name}_{pocoObj.Company}";
Убедитесь, что эта переменная определена в пределах обработки каждого элемента personPoco
.
2. Закрытие файлового потока
В вашем коде файл закрывается в конце метода GenerateAsync
. Однако, если произойдет исключение или ошибка во время записи в файл, это может привести к тому, что файл не будет закрыт корректно, и данные будут повреждены.
Рекомендация: Используйте конструкцию using
для управления жизненным циклом FileStream
, что обеспечивает автоматическое закрытие потока даже в случае возникновения исключений:
using (var fs = new FileStream(filepath, FileMode.Create, FileAccess.Write))
{
await fs.WriteAsync(byteResults);
}
Это гарантирует, что поток будет закрыт корректно.
3. Потенциальные проблемы с конфигурацией SSRS
Разница в поведении между двумя.GenerationAsync(для PDF и Excel) может быть вызвана различиями в конфигурации SSRS. Убедитесь, что используемые параметры
RenderRequest` корректны и соответствуют ожидаемому формату выходных данных. Проверьте, что данные передаются в процедуру корректно.
4. Асинхронные операции
Поскольку вы используете асинхронные операции, важно удостовериться, что порядок выполнения не влияет на доступ к общим данным. Хотя Parallel.ForEachAsync
управляет параллельным выполнением, структура вашего кода должна обеспечивать независимость каждого потока.
5. Ошибки в логике генерации
Код может содержать логические ошибки, которые не очевидны при запуске в однопоточном режиме. Проверьте, корректно ли обрабатывается каждый personDetail
и вызов GenerateAsync
для каждого элемента коллекции people
.
Заключение
Несоответствие имен файлов и их содержимого при использовании параллельной обработки может возникнуть по причине гонок потоков, проблем с конфигурацией SSRS или ошибок в обработке данных. Внедрение предложенных изменений улучшит процесс генерации отчетов и обеспечит соответствие между именами файлов и их содержимым. Рассмотрите возможность использования механизмов логирования для мониторинга и отладки при возникновении проблем.