CSharpCodeProvider throwing "Процесс не может получить доступ к файлу, потому что он используется другим процессом." на тот же файл, который он генерирует
... и это не потому, что файл был там с самого начала. Я могу доказать это.
Фон таков: у нас есть настольное приложение, которое генерирует WORD/PDF документы. Документ состоит из строительных блоков - небольших классов, которые создают определенный макет и могут быть подключены к разным источникам данных и которые динамически объединяются для создания документа. Я не думаю, что это актуально, но для полноты картины я использую ASPOSE для генерации документов.
Также было еще одно требование: мы должны быть в состоянии изменить код C# строительных блоков и сделать его доступным на производстве без необходимости делать релиз. Решение, которое мы выбрали, состояло в том, чтобы сохранить код для различных строительных блоков в базе данных и предварительно скомпилировать их во время запуска приложения: при запуске приложения оно получает код из базы данных и компилирует их в отдельные сборки, которые затем сбрасываются во временную папку приложения. Чтобы не допустить ошибки "файл используется другим процессом", я добавил метку даты и времени к названию каждой сборки - до миллисекунды. Я отслеживаю версию сборки, сохраняя метку даты и времени в статической переменной. Таким образом, если пользователь обновляет приложение или открывает другой экземпляр, он безопасно генерирует новый набор dll с уникальными именами, что - теоретически - гарантирует, что я никогда не получу исключение "Файл, используемый другим процессом". Когда все экземпляры приложения закрыты, временная папка стирается. (И у нас не было проблем с этим). В 99,9% случаев это решение отлично работает. Это быстро и эффективно, и поддерживает актуальность DLL, когда мы вносим изменения. НО!
Наш код обработки ошибок отправляет электронные письма на сервер, и я получаю сообщение об ошибке "Файл используется другим процессом"! В моем коде обработки ошибок я сделал следующее: Когда выдается исключение, я получаю список всех DLL-файлов, которые уже существуют в папке. У меня также есть список всех DLL, которые были сгенерированы во время этой итерации. Вся эта информация затем включается в сообщение об ошибке. Это только добавило путаницы: во всех случаях ошибка возникает в последнем файле, который был добавлен (не обновлен) в папку, без намека на то, что могло быть дублированное имя файла. Кроме того, ошибка никогда не возникает в том же файле. Иногда это происходит при создании самого первого файла, иногда это происходит только после того, как большинство файлов уже создано. Ошибка также случается так редко, что я не смог ее воспроизвести. Запрашивать у пользователя дополнительную информацию будет бесполезно, поскольку проблема возникает только в том случае, если он пытается сгенерировать отчет, и это может произойти через несколько часов после открытия приложения. Кроме того, этот процесс происходит еще до того, как пользователь действительно может что-либо сделать в приложении.
Я получаю сообщение об ошибке:
Сообщение: System.Exception: Произошла ошибка при компиляции E_Text ---> System.Exception: 8 Ошибки: Строка: 4 - Директива using для 'System' ранее появлялась в этом пространстве имен Строка: 5 - Директива using для 'System.Collections.Generic'ранее отображалось в этой строке пространства имен: 188 - 'C####l.Framework.Reporting.ReportWriter.GeneratorCoreExtendedBase.AddContentWithTags(string, bool, C####l.Framework.Reporting.ReportWriter.FontTemplateypes) устарел: "Вместо этого используйте параметр ContentWithTagsParameters, поскольку это упрощает добавление дополнительных параметров в дальнейшем". Строка: 153 - переменная ex объявлена, но никогда не используется Строка: 161 - переменная ex объявлена, но никогда не используется Строка: 171 - переменная ex объявлена, но никогда не используется Строка: 182 - переменная ex'объявлено, но никогда не используется Строка: 0 - Не удалось записать в выходной файл'c:\Users\a#####e\Documents\T###s\Temp\E_Text_201402141156300351.dll' -' Процесс не может получить доступ файл, потому что он используется другим процессом.'
ФАЙЛЫ, ДОБАВЛЕННЫЕ В ПАПКУ ТЕМПЕРАТУРЫ:
добавленный-C:\Users\a#####e\Documents\T###s\Temp\ExtendedGenerator_201402141156300351.dll добавленный-C:\Users\a#####e\Documents\T###s\Temp\E_Header_1_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\S_StyleDefault_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\E_Footer_1_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\E_Area_Line_Chart_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\H_ImageHeader_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\S_DefaultEditableContent_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\S_DefaultCoverLetter_201402141156300351.dll добавлен-C:\Users\a#####e\Documents\T###s\Temp\E_Client_Address_And_Salutation_201402141156300351.dll
ФАЙЛЫ, ТЕКУЩИЕ В ТЕМПЕРНОЙ ПАПКЕ: C:\Users\a#####e\Documents\T###s\Temp\ExtendedGenerator_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\E_Area_Line_Chart_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\E_Client_Address_And_Salutation_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\E_Footer_1_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\E_Header_1_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\E_Text_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\H_ImageHeader_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\S_DefaultCoverLetter_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\S_DefaultEditableContent_201402141156300351.dll C:\Users\a#####e\Documents\T###s\Temp\S_StyleDefault_201402141156300351.dll
в C####l.Framework.Entity.ReportingCompiler.Compile(String assemblyName, Строковые ссылки, String code, List`1 AddedFiles) --- Конец внутренней трассировки стека исключений --- в C####l.Framework.Entity.ReportingCompiler.PrecompileElements(Object o, DbTransaction& транзакция) в C####l.Windows.Controls.BackgroundProcess.worker_DoWork(Отправитель объекта, DoWorkEventArgs e) - при компиляции E_Text Stack Trace: в C#### произошла ошибка. Framework.Entity.ReportingCompiler.PrecompileElements (Объект o, DbTransaction & транзакция) в C####l.Windows.Controls.BackgroundProcess.worker_DoWork(Отправитель объекта, DoWorkEventArgs e) Приложение: #####
Версия: #. #. ##. ####
OSVersion: Microsoft Windows NT 6.1.7601 с пакетом обновления 1
Описание: не удалось прекомпилировать элемент отчетности
Код для компилятора выглядит следующим образом: Когда приложение запускается, оно вызывает метод PrecompileElements, а когда генерируются отчеты, оно вызывает метод LoadAssembly. используя Систему; using System.Collections.Generic; using System.CodeDom.Compiler; использование System.Text; используя System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Reflection; using System.Reflection.Emit; используя Microsoft.CSharp; используя C####l.Framework.Configuration; используя C####l.Framework.Mapping; использование System.Data.Common; используя C####l.Framework.Exceptions;
namespace C####l.Framework.Entity
{
public class ReportingCompiler
{
#region Members
private const string _assemblyVersionFile = "CurrentAssemblyVersion.txt";
private List<Guid> _compiledGuids = new List<Guid>();
private static string _assemblyVersion = "";
private static bool _assembliesCompilationStarted = false;
private static bool _isAssembliesCompiled = false;
private static bool _assembliesCompilationFailed = false;
private static string _assemblyLocation = "";
#endregion
#region Properties
public static bool IsAssembliesCompiled
{
get { return _isAssembliesCompiled; }
set { _isAssembliesCompiled = value; }
}
public static bool AssembliesCompilationFailed
{
get { return _assembliesCompilationFailed; }
set { _assembliesCompilationFailed = value; }
}
public static bool AssembliesCompilationStarted
{
get { return _assembliesCompilationStarted; }
}
/// <summary>
/// When set to an empty string, the value will default to the temporary folder.
/// </summary>
public static string AssemblyLocation
{
get
{
if (_assemblyLocation.Length == 0)
{
return GlobalSettings.TemporaryFolder;
}
else
{
return _assemblyLocation;
}
}
set { _assemblyLocation = value; }
}
#endregion
#region Methods
#region LoadAssembly
public static Assembly LoadAssembly(string className)
{
Assembly assembly = null;
if (!AssembliesCompilationStarted)
{
throw new FrameworkException("Cannot generate a report because the compilation of the reporting assemblies was never initiated.", false);
}
if (AssembliesCompilationFailed)
{
throw new FrameworkException("Unable to generate report because the reporting assemblies failed to compile. Please restart the application, and report the issue if the problem persist.", false);
}
else if (!IsAssembliesCompiled)
{
throw new FrameworkException("Reporting assemblies are still compiling. Please wait a few seconds, and try again.", false);
}
else
{
try
{
assembly = Assembly.LoadFile(GetFilePath(className));
}
catch (Exception ex)
{
List<string> existingFiles = GetExistingFiles();
string files = "\r\n\r\nFILES CURRENTLY IN TEMP FOLDER:\r\n";
for (int i = 0; i < existingFiles.Count; i++)
{
files = files + existingFiles[i] + "\r\n";
}
Exception ex2 = new Exception(files, ex);
throw new Exception("An error occured while trying to retrieve assembly \"" + GetFilePath(className) + "\"", ex2);
}
}
return assembly;
}
#endregion
#region PrecompileElements
public object PrecompileElements(object o, ref DbTransaction transaction)
{
_assembliesCompilationStarted = true;
_assembliesCompilationFailed = false;
_isAssembliesCompiled = false;
try
{
GlobalSettings.CheckAsposeLicense();
CreateVersionNumber();
ReportingExtendedGeneratorCoreCollection generators = (new ReportingExtendedGeneratorCoreMapper()).List();
ReportingDynamicClassDefinitionCollection elements = (new ReportingDynamicClassDefinitionMapper()).List();
string filePath = AssemblyLocation;
List<string> addedFiles = new List<string>();
for (int i = 0; i < generators.Count; i++)
{
Compile(generators[i].CSharpClassName, generators[i].CSharpReferences, generators[i].CSharpCode, addedFiles);
}
for (int i = 0; i < elements.Count; i++)
{
_isAssembliesCompiled = false;
Compile(elements[i].CSharpClassName, elements[i].CSharpReferences, elements[i].CSharpCode, addedFiles);
}
_isAssembliesCompiled = true;
return _isAssembliesCompiled;
}
catch (Exception ex)
{
_assembliesCompilationFailed = true;
throw ex;
}
}
#endregion
#endregion
#region Private methods
#region CreateVersionNumber
private void CreateVersionNumber()
{
_assemblyVersion = DateTime.Now.ToString("yyyyMMdd_HHmmss_ffff_");
StringBuilder sb = new StringBuilder(AssemblyLocation);
sb.Append(_assemblyVersionFile);
File.WriteAllText(sb.ToString(), _assemblyVersion);
}
#endregion
#region GetExistingFiles
private static List<string> GetExistingFiles()
{
List<string> currentFiles = null;
try
{
currentFiles = new List<string>(Directory.GetFiles(AssemblyLocation, "*.dll"));
}
catch (Exception ex)
{
currentFiles = new List<string>();
currentFiles.Add("Could not obtain list of existing files for the following reason: " + ex.Message);
}
return currentFiles;
}
#endregion
#region Compile
private string Compile(string assemblyName, string references, string code, List<string> addedFiles)
{
try
{
IDictionary<string, string> providerOptions = new Dictionary<string, string>();
providerOptions["CompilerVersion"] = "v3.5";
CodeDomProvider provider = new CSharpCodeProvider(providerOptions);
// ICodeCompiler compiler = new CSharpCodeProvider(providerOptions).CreateCompiler();
CompilerParameters compilerParameters = new CompilerParameters();
string[] referencedAssemblies = references.Split(';');
for (int i = 0; i < referencedAssemblies.Length; i++)
{
compilerParameters.ReferencedAssemblies.Add(referencedAssemblies[i]);
}
compilerParameters.GenerateInMemory = false; //true;
compilerParameters.OutputAssembly = GetFilePath(assemblyName);
CompilerResults compilerResults = provider.CompileAssemblyFromSource(compilerParameters, code);
if (compilerResults.Errors.HasErrors)
{
string lcErrorMsg = "";
// *** Create Error String
lcErrorMsg = compilerResults.Errors.Count.ToString() + " Errors:";
for (int x = 0; x < compilerResults.Errors.Count; x++)
lcErrorMsg = lcErrorMsg + "\r\nLine: " + compilerResults.Errors[x].Line.ToString() + " - " +
compilerResults.Errors[x].ErrorText;
lcErrorMsg = lcErrorMsg + "\r\n\r\nFILES ADDED TO THE TEMP FOLDER:\r\n";
for (int i = 0; i < addedFiles.Count; i++)
{
lcErrorMsg = lcErrorMsg + "added - " + addedFiles[i] + "\r\n";
}
List<string> existingFiles = GetExistingFiles();
lcErrorMsg = lcErrorMsg + "\r\n\r\nFILES CURRENTLY IN TEMP FOLDER:\r\n";
for (int i = 0; i < existingFiles.Count; i++)
{
lcErrorMsg = lcErrorMsg + existingFiles[i] + "\r\n";
}
throw new Exception(lcErrorMsg);
//MessageBox.Show(lcErrorMsg + "\r\n\r\n" + lcCode, "Compiler Demo", MessageBoxButtons.OK, MessageBoxIcon.Error);
//return;
}
else
{
addedFiles.Add(GetFilePath(assemblyName));
}
return compilerResults.PathToAssembly;//CompiledAssembly;
}
catch (Exception ex)
{
throw new Exception("An error occured while compiling " + assemblyName, ex);
}
}
#endregion
#region GetFilePath
private static string GetFilePath(string className)
{
StringBuilder sb = new StringBuilder(AssemblyLocation);
sb.Append(_assemblyVersion);
sb.Append(className);
sb.Append(".dll");
return sb.ToString();
}
#endregion
#endregion
}
}
Пока что я пришел к выводу, что исключение выдается из класса CSharpCodeProvider, что в значительной степени ограничивает то, что я могу с этим поделать. Почти как будто он создает файл, а затем - когда он хочет записать в него, он вызывает ошибку. Но это странно, потому что - насколько я понимаю - dotNet также использует этот же класс для компиляции проектов, и у меня никогда не было этой проблемы раньше.
Если у кого-то есть понимание этого, я буду очень признателен за ваш вклад.