Search code examples
c#.netwpf.net-assemblyapplicationsettingsbase

ApplicationSettingsBase ConfigurationErrorsException multiple assemblies


Updated Question (to point out the problem correctly)

I'm using a lib, which implements a class that derives from ApplicationSettingsBase.

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

Now I use this lib in another project. The projects also contains at least one class that derives from ApplicationSettingsBase.

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

Now both classes deriving from ApplicationSettingsBase storing their properties to same user.config file. The application and lib uses multiple tasks and if two tasks perform (for example) the properties setter at the same time I get the following exception. Both tasks try to perform write action at same time...

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)

I can reproduce it with the following scenario:

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;
});

Now I was looking for an implementation to protect the access to user.config file.

Solution:

I found a working solution. The CustomApplicationSettingsBase locks concurrent tasks.

public sealed class GlobalLibSettings : CustomApplicationSettingsBase and

public sealed class ProjectSettings : CustomApplicationSettingsBase with:

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();
            }
        }
    }
}

Thanks for your help!


Solution

  • The problem probably lays in the fact that you have more than one instances of GlobalAppSettings that are used at the same time. It means that there may be more than one thread trying to read/write the same file and this leads to the excpetion.

    Your solution with lock does not work because _locker object is not shared between different instances of GlobalAppSettings.

    I see the following solutions. Firstly, you can do something like in your second edit i.e. to use static object to synchronize access to settings. However, I like more the second solution. Try to implement CustomApplicationSettingsBase as the singleton. Or even better use dependency injection to inject an instance of CustomApplicationSettingsBase where it is needed and tell DI container that CustomApplicationSettingsBase should be the singleton.