c#题例-2025-08-02 09:52:13
日期: 2025-08-02 分类: AI写作 91次阅读
当然可以!下面是一道**专家级别**的 C# 程序员逻辑面试题,它不仅考察对 C# 语言特性的掌握,还涉及并发编程、线程安全、异步编程和设计模式的理解。
---
### 🧠 面试题:实现一个线程安全且支持异步的缓存系统(Cache)
#### 📌 题目描述:
你需要实现一个通用的缓存系统 `ConcurrentAsyncCache
1. **支持异步加载数据**:如果缓存中没有对应的键,则调用一个传入的异步工厂函数 `Func
2. **线程安全**:多个线程或任务可以同时访问该缓存而不会出现数据竞争。
3. **防止缓存击穿**:当多个线程同时请求一个缓存未命中项时,只允许一个线程执行加载操作,其余线程等待其完成。
4. **支持过期机制(可选加分)**:每个缓存项可以设置生存时间(TTL),自动清理过期项。
5. **支持清除缓存项**。
6. **异步清理过期缓存项(可选加分)**。
#### 📌 接口定义(简化版):
```csharp
public interface IAsyncCache
{
    Task
    void Remove(TKey key);
}
```
#### 📌 示例使用:
```csharp
var cache = new ConcurrentAsyncCache
var result1 = await cache.GetOrAddAsync("key1", async (k, ct) =>
{
    await Task.Delay(1000, ct); // 模拟耗时操作
    return "value1";
}, CancellationToken.None);
var result2 = await cache.GetOrAddAsync("key1", (k, ct) => throw new Exception("Should not be called again!"), CancellationToken.None);
// result2 应该等于 "value1"
```
---
### 🧩 考察点:
- 对 `ConcurrentDictionary
- 对 `TaskCompletionSource
- 异步编程与 `CancellationToken` 的结合。
- 线程安全设计与并发控制。
- 可选:定时清理机制(使用 `Timer` 或后台任务)。
- 可选:缓存过期策略(LRU、TTL 等)。
---
### ✅ 参考答案(简化实现):
```csharp
public class ConcurrentAsyncCache
{
    private readonly ConcurrentDictionary
    private readonly TimeSpan _defaultTtl;
    public ConcurrentAsyncCache(TimeSpan? defaultTtl = null)
    {
        _defaultTtl = defaultTtl ?? Timeout.InfiniteTimeSpan;
    }
    public async Task
        TKey key,
        Func
        TimeSpan? ttl = null,
        CancellationToken ct = default)
    {
        if (_cache.TryGetValue(key, out var entry) && !entry.IsExpired())
        {
            return entry.Value;
        }
        var tcs = new TaskCompletionSource
        var newEntry = new CacheEntry(default!, ttl ?? _defaultTtl);
        var added = _cache.TryAdd(key, newEntry);
        if (!added)
        {
            // 如果已经有其他线程在加载,等待它完成
            entry = _cache[key];
            if (!entry.IsExpired())
            {
                return entry.Value;
            }
            // 如果已经过期,尝试替换
            if (!_cache.TryUpdate(key, newEntry, entry))
            {
                // 如果替换失败,说明其他线程已经更新了缓存
                entry = _cache[key];
                if (!entry.IsExpired())
                {
                    return entry.Value;
                }
                else
                {
                    throw new InvalidOperationException("Conflict during cache update.");
                }
            }
        }
        try
        {
            var value = await factory(key, ct).ConfigureAwait(false);
            newEntry.SetValue(value);
            tcs.SetResult(value);
            return value;
        }
        catch (Exception ex)
        {
            _cache.TryRemove(key, out _);
            tcs.SetException(ex);
            throw;
        }
    }
    public void Remove(TKey key)
    {
        _cache.TryRemove(key, out _);
    }
    private class CacheEntry
    {
        private TValue _value;
        private readonly object _lock = new();
        private DateTime _created;
        private readonly TimeSpan _ttl;
        public CacheEntry(TValue value, TimeSpan ttl)
        {
            _value = value;
            _created = DateTime.UtcNow;
            _ttl = ttl;
        }
        public bool IsExpired()
        {
            if (_ttl == Timeout.InfiniteTimeSpan)
                return false;
            return DateTime.UtcNow - _created > _ttl;
        }
        public TValue Value => _value;
        public void SetValue(TValue value)
        {
            lock (_lock)
            {
                _value = value;
                _created = DateTime.UtcNow;
            }
        }
    }
}
```
---
### 🧪 拓展问题(面试追问):
- 如何测试这个缓存的线程安全性?
- 如果缓存的数据很大,如何优化内存使用?
- 如何支持 LRU 缓存策略?
- 如何避免死锁在异步代码中?
- 如何监控缓存命中率?
---
如果你需要我为这道题写一个完整的单元测试示例或扩展功能(如 TTL 自动清理),我也可以继续补充。希望这道题能帮助你筛选出真正有经验的 C# 工程师!
 除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
标签:AI写作
精华推荐

