具有异步操作的线程安全单例

Thread safe singleton with async operation

我有一个 ASP.NET MVC5 应用程序使用 Ninject 作为 DI。我有一条消息显示在每一页的顶部。使用异步操作从 Web 服务检索消息。消息本身很小,更新不频繁,所以想缓存在内存中。

我创建了一个配置为 DI 单例的简单服务。 ReaderWriterLock 我有一件好事,但它不支持异步。所以我尝试用 SemaphoreSlim 重新创建相同的东西。这就是我得到的:

    public class ExampleService {

        private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
        private DateTime? lastAccess = null;
        private string cachedMessage = null;

        public async Task<string> GetMessageAsync() {
            if (lastAccess.HasValue && lastAccess.Value > DateTime.UtcNow.AddHours(-1)) {
                return cachedMessage;
            }

            var writeable = semaphore.CurrentCount == 1;

            await semaphore.WaitAsync();
            try {
                if (writeable) {
                    // Do async stuff
                }
            }
            finally {
                semaphore.Release();
            }

            return cachedMessage;
        }

        ~ExampleService() {
            if (semaphore != null) semaphore.Dispose();
        }

    }

目标是让所有呼叫等到 cacheMessage 被填充,然后将其分配给所有人。我的解决方案的问题是,一旦写入调用完成,所有等待的读取实际上都被困在一个队列中,一个接一个地被释放,而新的读取则完全跳过队列。

执行此操作的更好方法是什么?

更新

基于此post, a lot of people appear to advocate just using LazyCache。实施:

    public class ExampleService {

        private readonly IAppCache cache;

        public ExampleService(IAppCache cache) {
            this.cache = cache;
        }

        private async Task<string> GetMessageFromServerAsync() {
            // result = async stuff
            return result;
        }

        public async Task<string> GetMessageAsync() {
            return await cache.GetOrAddAsync("MessageKey", GetMessageFromServerAsync);
        }

    }

基于此post, a lot of people appear to advocate just using LazyCache。实施:

public class ExampleService {

    private readonly IAppCache cache;

    public ExampleService(IAppCache cache) {
        this.cache = cache;
    }

    private async Task<string> GetMessageFromServerAsync() {
        // result = async stuff
        return result;
    }

    public async Task<string> GetMessageAsync() {
        return await cache.GetOrAddAsync("MessageKey", GetMessageFromServerAsync);
    }

}