如何解决在单独的线程中同步更新属性
我有一个从BackgroundService
继承的类。它在应用程序的整个生命周期内运行一个任务,在该任务中,我有一个循环进行一些计算,其中一些使用可设置的属性,这些属性可以从另一个线程进行设置。对于循环的一次迭代,我必须确保所有这些属性都不会更改,因此我所有的计算都使用相同的值进行。
我最初处理此问题的方式是让我的require属性实际上只是将一个动作粘贴在队列中,然后在循环的开始执行这些动作。所以,我最终得到这样的东西:
private MyConfig Config { get; set; }
private readonly ConcurrentQueue<Action> queue = new ConcurrentQueue<Action>();
public void UpdateConfig(MyConfig config)
{
queue.Enqueue(() => Config = config);
}
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
for (int i = queue.Count; i > 0; i--)
{
queue.TryDequeue(out Action element);
element?.Invoke();
}
}
//now do some stuff with the updated config and maybe other properties too
});
}
我觉得可能有更好的方法。我应该只锁定那些属性吗?在这一点上,我还不确定最好的解决方案。
解决方法
以下是我认为的标准模式:使用lock
对象来同步对共享字段的访问。
private readonly object _locker = new object();
private MyConfig _config;
public void UpdateConfig(MyConfig config)
{
lock (_locker) _config = config;
}
protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
MyConfig localConfig; lock (_locker) localConfig = _config;
// Do stuff with the localConfig. Don't use the shared _config field.
}
});
}
这应该是线程安全的,前提是MyConfig
类型是不可变的。它应该与class
或struct
MyConfig
都同样有效。
有许多解决此问题的方法。您的解决方案是有效的,但可能会遇到问题。例如:
protected async Task AnotherThread ()
{
var config = new MyConfig ();
config.Property1 = 50;
config.Property2 = 75;
UpdateConfig ( config );
// This statement breaks your threading approach.
config.Property1 = 47;
}
为防止该问题与副本一起使用。看一下下面的config类。该类将使我们能够以线程安全的方式创建副本。
public class MyConfig
{
private readonly object _sync = new object();
public Int32 _property1;
public Int32 Property1
{
get
{
lock ( _sync )
{
return _property1;
}
}
set
{
lock ( _sync )
{
_property1 = value;
}
}
}
public Int32 _property2;
public Int32 Property2
{
get
{
lock ( _sync )
{
return _property2;
}
}
set
{
lock ( _sync )
{
_property2 = value;
}
}
}
// this method will create a copy of the current configuration.
public MyConfig Copy ()
{
MyConfig copy = new MyConfig();
// locking here will prevent changes to the properties of this instance
lock ( _sync )
{
// copy all properties while changes from other threads are prevented.
copy.Property1 = Property1;
copy.Property2 = Property2;
}
// changes are allowed to properties of this instance again.
return copy;
}
}
然后
protected async override Task ExecuteAsync ( CancellationToken stoppingToken )
{
await Task.Run ( async () =>
{
while ( !stoppingToken.IsCancellationRequested )
{
MyConfig copy = Config.Copy ();
// nothing outside of this thread has a reference to this copy of the configuration...
// so unless you do something silly in your Calculations it will be safe to use.
// In addition,since the properties of MyConfig are thread-safe,// Config can be edited from other threads.
Calculations ( copy );
}
} );
}
最后,由于我们对config类的更改,我们的update方法实际上不需要担心线程安全性。
public void UpdateConfig ( MyConfig config )
{
// update reference to new object
Config = config;
}
编辑:响应评论。
另一个选项是不可变的Config类。我不会详细介绍实现细节,但这使您可以自由共享参考,而不必担心对象发生变异。
最后,在第一个解决方案中,可以使用许多不同的库来防止代码膨胀和错误。可能值得检查一下AutoMapper。
,我倾向于将您的代码更改为此:
Group-Object { $_.BaseName.Split('\ \(\d\)',2)[0] } |
这确保更新配置的唯一方法是通过此方法。您只在班上的private MyConfig Config { get; set; }
private readonly ConcurrentQueue<Action<MyConfig>> queue = new ConcurrentQueue<Action<MyConfig>>();
public void UpdateConfig(Action<MyConfig> configAction)
{
queue.Enqueue(configAction);
}
protected async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(() =>
{
while (!stoppingToken.IsCancellationRequested)
{
for (int i = queue.Count; i > 0; i--)
{
queue.TryDequeue(out Action<MyConfig> element);
element?.Invoke(this.Config);
}
}
});
}
变量上工作。
我还担心同时调用private MyConfig Config { get; set; }
,因此建议您一次只进行大量出队操作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。