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 также использует этот же класс для компиляции проектов, и у меня никогда не было этой проблемы раньше.

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

0 ответов

Другие вопросы по тегам