Young87

SmartCat's Blog

So happy to code my life!

游戏开发交流QQ群号60398951

当前位置:首页 >AI写作

c#题例-2025-07-19 03:25:44

当然可以!下面是一道**专家级别**的 C# 程序员逻辑面试题,涉及 **多线程、锁机制、死锁预防、异步编程模型** 和 **设计模式理解**,适合考察高级开发者的综合能力。

---

### 🧠 面试题:实现一个线程安全的缓存刷新机制

#### 背景描述:

你正在开发一个高性能的缓存服务,该服务支持自动刷新缓存项(基于过期时间),并且支持多线程并发访问。你需要实现一个 `CacheService` 类,它具有以下功能:

- 支持存储键值对(`string key`, `string value`)。
- 每个缓存项都有一个过期时间(`TimeSpan`)。
- 缓存项在访问时如果已过期,则需要**异步刷新**(从数据库或远程服务重新加载),**但只允许一个线程发起刷新请求**(防击穿)。
- 在刷新期间,允许其他线程获取旧值(如果存在),直到刷新完成。
- 不允许出现死锁,并且要尽量减少锁的粒度以提高并发性能。

---

### ✅ 面试要求:

请实现以下接口:

```csharp
public interface ICacheService
{
Task GetOrRefreshAsync(string key, Func> refreshFunc, TimeSpan expiration, CancellationToken ct);
void Remove(string key);
}
```

其中:

- `GetOrRefreshAsync`:获取缓存值。如果缓存不存在或已过期,则触发刷新。
- `refreshFunc`:刷新缓存的函数,参数是 key,返回新的 value。
- `Remove`:手动移除缓存项。

---

### 🔍 考察点:

1. **线程安全与并发控制**:
- 使用合适的锁机制(如 `ConcurrentDictionary`, `SemaphoreSlim`, `AsyncLock` 等)。
- 避免缓存击穿(只有一个线程刷新)。
- 允许多个线程读取旧值(读写分离)。

2. **异步编程模型**:
- 正确使用 `Task`, `ConfigureAwait(false)`,避免死锁。
- 合理使用 `CancellationToken`。

3. **资源管理与性能优化**:
- 减少锁竞争,避免全局锁。
- 合理管理缓存项的生命周期和清理策略(可选扩展)。

4. **异常处理与健壮性**:
- 刷新失败时如何处理?是否保留旧值?

---

### 💡 提示(可引导候选人):

- 可使用 `ConcurrentDictionary` 来保存缓存。
- 对每个 key 使用独立的 `SemaphoreSlim` 或 `AsyncKeyLock` 来控制刷新并发。
- 使用 `ValueTask` 可进一步优化性能(可选)。
- 可以考虑使用 `MemoryCache` 或 `IMemoryCache` 作为底层实现(如果是 .NET Core/5+)。

---

### ✅ 示例代码(参考实现):

```csharp
public class CacheService : ICacheService
{
private class CacheItem
{
public string Value { get; set; }
public DateTime Expiration { get; set; }
public Task RefreshTask { get; set; }
}

private readonly ConcurrentDictionary _cache = new();
private readonly ConcurrentDictionary _locks = new();

public async Task GetOrRefreshAsync(
string key,
Func> refreshFunc,
TimeSpan expiration,
CancellationToken ct)
{
if (_cache.TryGetValue(key, out var item) && item.Expiration > DateTime.UtcNow)
{
return item.Value;
}

var gate = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));

await gate.WaitAsync(ct).ConfigureAwait(false);
try
{
// 双重检查
if (_cache.TryGetValue(key, out item) && item.Expiration > DateTime.UtcNow)
{
return item.Value;
}

// 启动刷新任务
item = new CacheItem
{
RefreshTask = RefreshInternal(key, refreshFunc, expiration)
};
_cache[key] = item;

// 等待刷新完成
await item.RefreshTask.ConfigureAwait(false);

return item.Value;
}
finally
{
gate.Release();
}
}

private async Task RefreshInternal(string key, Func> refreshFunc, TimeSpan expiration)
{
try
{
var newValue = await refreshFunc(key).ConfigureAwait(false);
_cache[key] = new CacheItem
{
Value = newValue,
Expiration = DateTime.UtcNow + expiration
};
return newValue;
}
catch
{
// 可选择保留旧值或标记为失败
return _cache.TryGetValue(key, out var old) ? old.Value : null;
}
}

public void Remove(string key)
{
_cache.TryRemove(key, out _);
_locks.TryRemove(key, out var gate);
gate?.Dispose();
}
}
```

---

### 🧪 延伸问题(可选):

- 如何处理缓存雪崩?
- 如果刷新函数非常耗时,如何避免影响主线程?
- 如何为缓存项添加滑动过期(Sliding Expiration)?
- 如何测试这个缓存服务的并发安全性?

---

如果你需要,我也可以提供单元测试示例或 Mock 测试场景。希望这道题能帮助你考察候选人的综合能力!需要其他类型的题目也可以告诉我。

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇:无

下一篇: c#题例-2025-07-18 21:52:20

精华推荐