带有一次性对象的 C# 单例
C# Singleton with a Disposable object
假设我有一个Singleton,它在创建时将资源加载到内存中,并在调用其方法时对数据进行操作。
现在假设,我希望能够告诉 Singleton 释放这些资源,因为我不希望在不久的将来使用它们,但也能够在时机成熟时重新加载这些资源。我希望这一切都是线程安全的。
解决这个问题的最佳方法是什么?
这个例子行得通吗?:
// Singleton implementation
...
private IDisposable resource;
private bool loadingResources;
private IDisposable Resource {
get => resource ?? throw new CustomException();
}
// Method A
public void A() {
var resource = Resource; // Throws CustomException if resource is null
// Do stuff
}
// Method B
public void B() {
var resource = Resource;
// Do stuff
}
public void ReleaseResources() {
if (resource != null)
lock (thislock) {
//resource.Dispose();
resource = null;
}
}
public void LoadResources() {
if (!loadingResources && resource == null)
lock (thislock)
if (!loadingResources && resource == null)
{
loadingResources = true;
// Load resources
resource = CreateResource();
loadingResources = false;
}
}
我建议将资源处理与实际使用分开。假设资源需要处理,这可能类似于:
public class DisposableWrapper<T> where T : IDisposable
{
private readonly Func<T> resourceFactory;
private T resource;
private bool constructed;
private object lockObj = new object();
private int currentUsers = 0;
public DisposableWrapper(Func<T> resourceFactory)
{
this.resourceFactory = resourceFactory;
}
public O Run<O>(Func<T, O> func)
{
lock (lockObj)
{
if (!constructed)
{
resource = resourceFactory();
constructed = true;
}
currentUsers++;
}
try
{
return func(resource);
}
catch
{
return default;
}
finally
{
Interlocked.Decrement(ref currentUsers);
}
}
public void Run(Action<T> action)
{
lock (lockObj)
{
if (!constructed)
{
resource = resourceFactory();
constructed = true;
}
currentUsers++;
}
try
{
action(resource);
}
finally
{
Interlocked.Decrement(ref currentUsers);
}
}
public bool TryRelease()
{
lock (lockObj)
{
if (currentUsers == 0 && constructed)
{
constructed = false;
resource.Dispose();
resource = default;
return true;
}
return false;
}
}
}
如果资源不需要处理,我建议改为使用 lazy<T>
。释放资源只是意味着用一个新对象替换现有的惰性对象。让垃圾收集器清理旧对象。
假设我有一个Singleton,它在创建时将资源加载到内存中,并在调用其方法时对数据进行操作。 现在假设,我希望能够告诉 Singleton 释放这些资源,因为我不希望在不久的将来使用它们,但也能够在时机成熟时重新加载这些资源。我希望这一切都是线程安全的。
解决这个问题的最佳方法是什么?
这个例子行得通吗?:
// Singleton implementation
...
private IDisposable resource;
private bool loadingResources;
private IDisposable Resource {
get => resource ?? throw new CustomException();
}
// Method A
public void A() {
var resource = Resource; // Throws CustomException if resource is null
// Do stuff
}
// Method B
public void B() {
var resource = Resource;
// Do stuff
}
public void ReleaseResources() {
if (resource != null)
lock (thislock) {
//resource.Dispose();
resource = null;
}
}
public void LoadResources() {
if (!loadingResources && resource == null)
lock (thislock)
if (!loadingResources && resource == null)
{
loadingResources = true;
// Load resources
resource = CreateResource();
loadingResources = false;
}
}
我建议将资源处理与实际使用分开。假设资源需要处理,这可能类似于:
public class DisposableWrapper<T> where T : IDisposable
{
private readonly Func<T> resourceFactory;
private T resource;
private bool constructed;
private object lockObj = new object();
private int currentUsers = 0;
public DisposableWrapper(Func<T> resourceFactory)
{
this.resourceFactory = resourceFactory;
}
public O Run<O>(Func<T, O> func)
{
lock (lockObj)
{
if (!constructed)
{
resource = resourceFactory();
constructed = true;
}
currentUsers++;
}
try
{
return func(resource);
}
catch
{
return default;
}
finally
{
Interlocked.Decrement(ref currentUsers);
}
}
public void Run(Action<T> action)
{
lock (lockObj)
{
if (!constructed)
{
resource = resourceFactory();
constructed = true;
}
currentUsers++;
}
try
{
action(resource);
}
finally
{
Interlocked.Decrement(ref currentUsers);
}
}
public bool TryRelease()
{
lock (lockObj)
{
if (currentUsers == 0 && constructed)
{
constructed = false;
resource.Dispose();
resource = default;
return true;
}
return false;
}
}
}
如果资源不需要处理,我建议改为使用 lazy<T>
。释放资源只是意味着用一个新对象替换现有的惰性对象。让垃圾收集器清理旧对象。