簡體   English   中英

ApplicationSettingsBase ConfigurationErrorsException 多個程序集

[英]ApplicationSettingsBase ConfigurationErrorsException multiple assemblies

更新問題(正確指出問題)

我正在使用一個庫,它實現了一個派生自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文件中。 應用程序和庫使用多個任務,如果兩個任務同時執行(例如)屬性設置器,我會收到以下異常。 兩個任務都嘗試同時執行寫入操作...

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

謝謝你的幫助!

System.Configuration 有很多不喜歡的地方,但它並沒有把這個細節弄錯。 您可以在Reference Source 中看到的內容,文件打開方式為:

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

如此簡單的讀取訪問並使用 FileShare.Read 允許其他任何人也讀取文件。 所以任何線程都可能觸發這段代碼,你不能得到文件共享異常。

所以你嘗試過的解決方案並不能解決問題。 使用提供的信息很難找到另一種解釋。 很難在如此隱藏的文件上發生共享沖突。 唯一合理的解釋是另一個線程在同一時間寫入文件。 換句話說,執行 Save() 方法。

你一定是非常不走運。 但技術上是可行的。 使用 Debug > Windows > Threads 調試器窗口查看其他線程正在做什么,您應該在它們的堆棧跟蹤之一中看到 Save() 方法調用。 唯一的另一個可能的怪癖是環境,不穩定的反惡意軟件可以在掃描文件時任意使文件無法訪問。

最后但並非最不重要的一點是,你做了什么使這項工作完全不明顯。 只有解決方案的 EXE 項目才能使用 .config 文件。 要讓 DLL 使用設置,需要很多不明顯的手帕。 我們不知道您做了什么,請務必使用這些詳細信息更新您的問題。 真的最好不要這樣做。

問題可能在於您同時使用了多個GlobalAppSettings實例。 這意味着可能有多個線程試圖讀/寫同一個文件,這會導致異常。

您的帶鎖解決方案不起作用,因為_locker對象不在GlobalAppSettings不同實例之間共享。

我看到以下解決方案。 首先,您可以在第二次編輯中執行類似操作,即使用靜態對象同步對設置的訪問。 但是,我更喜歡第二種解決方案。 嘗試將CustomApplicationSettingsBase實現為單例。 或者甚至更好地使用依賴注入在需要的地方注入CustomApplicationSettingsBase的實例,並告訴 DI 容器CustomApplicationSettingsBase應該是單例。

這是一個允許程序的多個副本同時運行的解決方案,例如,它在多線程支持之上添加了多進程支持。 最后保存的程序將“獲勝”。 如果在程序 1 保存后程序 2 保存了相同的設置。 程序 1 不會收到有關新值的通知。

根據您使用的這些設置,這可能不是問題。 例如,如果您要保存最大化的窗口狀態,或者諸如此類的瑣碎事情,則此解決方案效果很好。

如果您希望將新保存的值重新加載到第二個實例中,您可以在每個 getter 函數中手動調用 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;
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM