ApplicationSettingsBase ConfigurationErrorsException 多个程序集
ApplicationSettingsBase ConfigurationErrorsException multiple assemblies
更新问题(正确指出问题)
我正在使用一个库,它实现了一个从 ApplicationSettingsBase
.
派生的 class
namespace MyLib {
public sealed class GlobalLibSettings : ApplicationSettingsBase
{
[UserScopedSetting, DefaultSettingValue("true")]
public bool SimpleProperty{
get { return (bool) this["SimpleProperty"]; }
set {
this["SimpleProperty"] = value;
Save();
}
}
}
}
现在我在另一个项目中使用这个库。这些项目还包含至少一个 class 派生自 ApplicationSettingsBase
.
namespace MyProject {
public sealed class ProjectSettings : ApplicationSettingsBase
{
[UserScopedSetting, DefaultSettingValue("true")]
public bool AnotherProperty{
get { return (bool) this["AnotherProperty"]; }
set {
this["AnotherProperty"] = value;
Save();
}
}
}
}
现在两个 class 都从 ApplicationSettingsBase
派生,将它们的属性存储到同一个 user.config
文件中。应用程序和库使用多个任务,如果两个任务同时执行(例如)属性 setter,我会得到以下异常。两个任务都尝试同时执行写入操作...
System.Configuration.ConfigurationErrorsException occurred
BareMessage=Beim Laden einer Konfigurationsdatei ist ein Fehler aufgetreten.: Der Prozess kann nicht auf die Datei "... _xneb3g43uoxqiagk4ge5e4hea1vxlina.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird.
Filename=..._xneb3g43uoxqiagk4ge5e4hea1vxlina.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.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird. (..._xneb3g43uoxqiagk4ge5e4hea1vxlina.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.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 debugger window 查看其他线程在做什么,您应该在它们的堆栈跟踪之一中看到 Save() 方法调用。唯一可能的怪癖是环境问题,不稳定的反恶意软件可以在扫描文件时任意使文件无法访问。
最后但并非最不重要的一点是,您为完成这项工作所做的工作根本不明显。只有解决方案的 EXE 项目可以使用 .config 文件。需要大量不明显的花招才能让 DLL 使用设置。我们不知道您做了什么,请务必使用这些详细信息更新您的问题。真的最好不要这样做。
问题可能在于您同时使用了多个 GlobalAppSettings
实例。这意味着可能有多个线程试图 read/write 同一个文件,这会导致异常。
您的锁解决方案不起作用,因为 _locker
对象未在 GlobalAppSettings
的不同实例之间共享。
我看到了以下解决方案。首先,您可以在第二次编辑中做一些类似的事情,即使用静态对象来同步对设置的访问。但是,我更喜欢第二种解决方案。尝试将 CustomApplicationSettingsBase
实现为单例。或者甚至更好地使用依赖注入在需要的地方注入 CustomApplicationSettingsBase
的实例,并告诉 DI 容器 CustomApplicationSettingsBase
应该是单例。
这是一个允许程序的多个副本同时 运行 的解决方案,例如,它在多线程支持之上添加了多进程支持。最后保存的程序将“获胜”。如果程序 2 在程序 1 保存后保存相同的设置。程序 1 不会收到有关新值的通知。
根据您的用途,这些设置可能不是问题。例如,如果您正在保存最大化的 window 状态,或类似的微不足道的东西,此解决方案效果很好。
如果您希望新保存的值加载回第二个实例,您可以在每个 getter 函数中手动调用 Settings.Default.Reload() 以确保每次访问时都重新加载.这显然会增加相当多的开销,所以只有在您确实需要时才这样做。
如果当前有异常我什么都不做,但你也可以添加一些错误处理。
这使用命名的 eventWaitHandle。通过使用命名的事件句柄,它将允许您执行多进程锁定。通过将其设置为 AutoReset,它将在程序 crash/exit 等上自动解锁,使用起来非常安全。
锁名称是.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;
}
}
更新问题(正确指出问题)
我正在使用一个库,它实现了一个从 ApplicationSettingsBase
.
namespace MyLib {
public sealed class GlobalLibSettings : ApplicationSettingsBase
{
[UserScopedSetting, DefaultSettingValue("true")]
public bool SimpleProperty{
get { return (bool) this["SimpleProperty"]; }
set {
this["SimpleProperty"] = value;
Save();
}
}
}
}
现在我在另一个项目中使用这个库。这些项目还包含至少一个 class 派生自 ApplicationSettingsBase
.
namespace MyProject {
public sealed class ProjectSettings : ApplicationSettingsBase
{
[UserScopedSetting, DefaultSettingValue("true")]
public bool AnotherProperty{
get { return (bool) this["AnotherProperty"]; }
set {
this["AnotherProperty"] = value;
Save();
}
}
}
}
现在两个 class 都从 ApplicationSettingsBase
派生,将它们的属性存储到同一个 user.config
文件中。应用程序和库使用多个任务,如果两个任务同时执行(例如)属性 setter,我会得到以下异常。两个任务都尝试同时执行写入操作...
System.Configuration.ConfigurationErrorsException occurred
BareMessage=Beim Laden einer Konfigurationsdatei ist ein Fehler aufgetreten.: Der Prozess kann nicht auf die Datei "... _xneb3g43uoxqiagk4ge5e4hea1vxlina.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird.
Filename=..._xneb3g43uoxqiagk4ge5e4hea1vxlina.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.0.4.862\user.config" zugreifen, da sie von einem anderen Prozess verwendet wird. (..._xneb3g43uoxqiagk4ge5e4hea1vxlina.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.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 debugger window 查看其他线程在做什么,您应该在它们的堆栈跟踪之一中看到 Save() 方法调用。唯一可能的怪癖是环境问题,不稳定的反恶意软件可以在扫描文件时任意使文件无法访问。
最后但并非最不重要的一点是,您为完成这项工作所做的工作根本不明显。只有解决方案的 EXE 项目可以使用 .config 文件。需要大量不明显的花招才能让 DLL 使用设置。我们不知道您做了什么,请务必使用这些详细信息更新您的问题。真的最好不要这样做。
问题可能在于您同时使用了多个 GlobalAppSettings
实例。这意味着可能有多个线程试图 read/write 同一个文件,这会导致异常。
您的锁解决方案不起作用,因为 _locker
对象未在 GlobalAppSettings
的不同实例之间共享。
我看到了以下解决方案。首先,您可以在第二次编辑中做一些类似的事情,即使用静态对象来同步对设置的访问。但是,我更喜欢第二种解决方案。尝试将 CustomApplicationSettingsBase
实现为单例。或者甚至更好地使用依赖注入在需要的地方注入 CustomApplicationSettingsBase
的实例,并告诉 DI 容器 CustomApplicationSettingsBase
应该是单例。
这是一个允许程序的多个副本同时 运行 的解决方案,例如,它在多线程支持之上添加了多进程支持。最后保存的程序将“获胜”。如果程序 2 在程序 1 保存后保存相同的设置。程序 1 不会收到有关新值的通知。
根据您的用途,这些设置可能不是问题。例如,如果您正在保存最大化的 window 状态,或类似的微不足道的东西,此解决方案效果很好。
如果您希望新保存的值加载回第二个实例,您可以在每个 getter 函数中手动调用 Settings.Default.Reload() 以确保每次访问时都重新加载.这显然会增加相当多的开销,所以只有在您确实需要时才这样做。
如果当前有异常我什么都不做,但你也可以添加一些错误处理。
这使用命名的 eventWaitHandle。通过使用命名的事件句柄,它将允许您执行多进程锁定。通过将其设置为 AutoReset,它将在程序 crash/exit 等上自动解锁,使用起来非常安全。
锁名称是.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;
}
}