ApplicationSettingsBase ConfigurationErrorsException несколько сборок

Обновленный вопрос (чтобы правильно указать на проблему)

Я использую библиотеку, которая реализует класс, производный от ApplicationSettingsBase,

namespace MyLib {
    public sealed class GlobalLibSettings : ApplicationSettingsBase
    {
        [UserScopedSetting, DefaultSettingValue("true")]
        public bool SimpleProperty{
            get { return (bool) this["SimpleProperty"]; }
            set {
                this["SimpleProperty"] = value;
                Save();
            }
        }
    }
}

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

namespace MyProject {
    public sealed class ProjectSettings : ApplicationSettingsBase
    {
        [UserScopedSetting, DefaultSettingValue("true")]
        public bool AnotherProperty{
            get { return (bool) this["AnotherProperty"]; }
            set {
                this["AnotherProperty"] = value;
                Save();
            }
        }
    }
}

Теперь оба класса происходят от ApplicationSettingsBase сохраняя свои свойства к тому же user.config файл. Приложение и lib используют несколько задач, и если две задачи выполняют (например) установщик свойств одновременно, я получаю следующее исключение. Обе задачи пытаются выполнить действие записи одновременно...

System.Configuration.ConfigurationErrorsException occurred
  BareMessage=Beim Laden einer Konfigurationsdatei ist ein Fehler aufgetreten.: Der Prozess kann nicht auf die Datei "... _xneb3g43uoxqiagk4ge5e4hea1vxlina\1.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird.
  Filename=..._xneb3g43uoxqiagk4ge5e4hea1vxlina\1.0.4.862\user.config
  HResult=-2146232062
  Line=0
  Message=Beim Laden einer Konfigurationsdatei ist ein Fehler aufgetreten.: Der Prozess kann nicht auf die Datei "..._xneb3g43uoxqiagk4ge5e4hea1vxlina\1.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird. (..._xneb3g43uoxqiagk4ge5e4hea1vxlina\1.0.4.862\user.config)
  Source=System.Configuration
  StackTrace:
       bei System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
  InnerException: 
       HResult=-2147024864
       Message=Der Prozess kann nicht auf die Datei "_xneb3g43uoxqiagk4ge5e4hea1vxlina\1.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird.
       Source=mscorlib
       StackTrace:
            bei System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
            bei System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
            bei System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
            bei System.Configuration.Internal.InternalConfigHost.StaticOpenStreamForRead(String streamName)
            bei System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.OpenStreamForRead(String streamName, Boolean assertPermissions)
            bei System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.OpenStreamForRead(String streamName)
            bei System.Configuration.ClientConfigurationHost.OpenStreamForRead(String streamName)
            bei System.Configuration.BaseConfigurationRecord.RefreshFactoryRecord(String configKey)

Я могу воспроизвести его по следующему сценарию:

var settings1 = new GlobalLibSettings ();
var settings2 = new ProjectSettings ();
Task.Factory.StartNew(()=>{
    while(true) settings1.SimpleProperty = !settings1.SimpleProperty;
});
Task.Factory.StartNew(()=>{
    while(true) settings2.AnotherProperty = !settings2.AnotherProperty;
});

Теперь я искал реализацию для защиты доступа к user.config файл.

Решение:

Я нашел рабочее решение. CustomApplicationSettingsBase блокирует параллельные задачи.

public sealed class GlobalLibSettings : CustomApplicationSettingsBase а также

public sealed class ProjectSettings : CustomApplicationSettingsBase с:

namespace MyLib {
    public static class LockProvider
    {
        public static object AppSettingsLock { get; } = new object();
    }

    public class CustomApplicationSettingsBase : ApplicationSettingsBase
    {
        public override object this[string propertyName] {
            get {
                lock (LockProvider.AppSettingsLock) {
                    return base[propertyName];
                }
            }
            set {
                lock (LockProvider.AppSettingsLock) {
                    base[propertyName] = value;
                }
            }
        }
        public override void Save() {
            lock (LockProvider.AppSettingsLock) {
                base.Save();
            }
        }
        public override void Upgrade() {
            lock (LockProvider.AppSettingsLock) {
                base.Upgrade();
            }
        }
    }
}

Спасибо за вашу помощь!

3 ответа

Решение

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

Ваше решение с блокировкой не работает, потому что _locker Объект не разделяется между различными экземплярами GlobalAppSettings,

Я вижу следующие решения. Во-первых, вы можете сделать что-то подобное во втором редактировании, т.е. использовать статический объект для синхронизации доступа к настройкам. Однако мне больше нравится второе решение. Попробуй реализовать CustomApplicationSettingsBase как синглтон. Или даже лучше использовать внедрение зависимостей, чтобы внедрить экземпляр CustomApplicationSettingsBase где это необходимо и сказать DI-контейнер, что CustomApplicationSettingsBase должен быть единственным.

В System.Configuration есть что не нравиться, но она не ошибается в деталях. То, что вы можете увидеть в справочном источнике, файл открывается с помощью:

 return new FileStream(streamName, FileMode.Open, FileAccess.Read, FileShare.Read);

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

Таким образом, решение, которое вы попробовали, не может решить проблему. Найти другое объяснение будет сложно с предоставленной информацией. Очень трудно получить конфликт совместного доступа к файлу, который так хорошо скрыт. Единственное правдоподобное объяснение - то, что другой поток пишет в файл в то же самое время. Другими словами, выполнение метода Save().

Тебе должно быть необычайно не повезло. Но это технически возможно. Используйте окно Debug > Windows > Threads debugger, чтобы увидеть, что делают другие потоки, вы должны увидеть вызов метода Save() в одном из их следов стека. Единственная другая возможная особенность - это экологическая защита: вредоносное антивирусное ПО может произвольно сделать файл недоступным во время сканирования файла.

И последнее, но не менее важное, это не очевидно, что вы сделали, чтобы сделать эту работу вообще. Только EXE-проект решения может использовать файл.config. Много неочевидного hanky-panky необходимо, чтобы заставить DLL использовать настройки. Мы не знаем, что вы сделали, обязательно уточните свой вопрос с этими деталями. Действительно, лучше не делать этого вообще.

Вот решение, которое позволяет запускать несколько копий программы одновременно, например, оно добавляет поддержку нескольких процессов поверх вашей поддержки нескольких потоков. Программа, которая сохранит последней, «выиграет». Если программа 2 сохраняет ту же настройку после сохранения программы 1. Программа 1 не получит уведомления о новом значении.

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

Если вы хотите, чтобы новые сохраненные значения загружались обратно во второй экземпляр, вы можете вручную вызвать Settings.Default.Reload() в каждой из функций получения, чтобы гарантировать, что они перезагружаются при каждом доступе. Это, очевидно, добавит немало накладных расходов, поэтому делайте это только в том случае, если вы действительно этого хотите.

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

Здесь используется именованный eventWaitHandle. Использование именованного дескриптора события позволит вам выполнить блокировку нескольких процессов. Установив его на AutoReset, он будет автоматически разблокироваться при сбое программы / выходе и т. Д., Что делает его очень безопасным в использовании.

Имя блокировки - это имя файла .config. Если вы используете общий файл .config, вам следует обновить имя блокировки до постоянного значения, а не использовать путь выполнения.

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

      public class ConfigSettings : IConfigSettings
{
    private readonly EventWaitHandle _configSettingsLock = new EventWaitHandle(true, EventResetMode.AutoReset, MakeStringPathSafe(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "program.exe.config")));


    public string SettingOne
    {
        set
        {
            LockConfigSettings();
            try 
            { 
                Properties.Settings.Default.SettingOne = value; 
                Properties.Settings.Default.Save();
            } 
            catch { }
            finally { ReleaseSettingsLock(); }
        }
        get
        {
            LockConfigSettings();
            try 
            { 
                return Properties.Settings.Default.SettingOne; 
            } 
            catch 
            { 
                return null; 
            }
            finally 
            { 
                ReleaseSettingsLock(); 
            }
                
        }
    }
    
    public string SettingTwo
    {
        set
        {
            LockConfigSettings();
            try 
            { 
                Properties.Settings.Default.SettingTwo = value; 
                Properties.Settings.Default.Save();
            } 
            catch { }
            finally { ReleaseSettingsLock(); }
        }
        get
        {
            LockConfigSettings();
            try 
            { 
                return Properties.Settings.Default.SettingTwo; 
            } 
            catch 
            { 
                return null; 
            }
            finally 
            { 
                ReleaseSettingsLock(); 
            }
                
        }
    }
    
    private void LockConfigSettings()
    {
        //In case we are debugging we make it infinite as the event handle will still count when the debugger is paused
        if (Debugger.IsAttached)
        {
            if (!_configSettingsLock.WaitOne())
                throw new InvalidOperationException($"Failed to lock program.exe.config due to timeout. Please try closing all instances of program.exe via task manager.");
        }
        else
        {
            //After 15 seconds stop waiting. This will ensure you get a crash or something other than an infinite wait.
            //This should only occur if the other application holding the lock has been paused in a debugger etc.
            if (!_configSettingsLock.WaitOne(15000))
                throw new InvalidOperationException($"Failed to lock program.exe.config due to timeout. Please try closing all instances of program.exe via task manager.");
        }
    }
    
    private void ReleaseSettingsLock()
    {
        try
        {
            _configSettingsLock.Set();
        }
        catch (Exception e)
        {
            throw new Exception($"Failed to release program.exe.config lock due to error");
        }
    }

    public static string MakeStringPathSafe(string path)
    {
        if (path == null) return path;

        path = path.Replace("//", "_");
        path = path.Replace("/", "_");
        path = path.Replace("\\", "_");
        path = path.Replace(@"\", "_");
        path = path.Replace(@"\\", "_");
        path = path.Replace(":", "-");
        path = path.Replace(" ", "-");
        return path;
    }

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