带有一次性对象的 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>。释放资源只是意味着用一个新对象替换现有的惰性对象。让垃圾收集器清理旧对象。