c#题例-2025-08-10 08:31:49
日期: 2025-08-10 分类: AI写作 21次阅读
当然可以!下面是一道**专家级别**的 C# 程序员逻辑面试题,涉及到 **委托、事件、闭包、线程安全、异步编程、捕获上下文**等多个高级概念:
---
### 🧠 面试题:闭包陷阱与异步事件的陷阱
**题目:**
考虑以下 C# 控制台程序:
```csharp
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
var service = new EventService();
for (int i = 0; i < 5; i++)
{
int counter = i;
Task.Run(async () =>
{
await Task.Delay(100); // 模拟异步操作
service.OnUpdate += (s, e) =>
{
Console.WriteLine($"Task {counter}: {e.Message}");
};
});
}
service.RaiseEvent("Hello");
Console.ReadLine();
}
}
public class EventService
{
public event EventHandler
public void RaiseEvent(string message)
{
OnUpdate?.Invoke(this, new UpdateEventArgs { Message = message });
}
}
public class UpdateEventArgs : EventArgs
{
public string Message { get; set; }
}
```
---
### ❓问题:
**1. 请分析这段代码输出的结果(如果有的话),并解释为什么?**
**2. 如果我们期望每个 Task 注册的事件处理器都能输出正确的 `counter` 值,应该如何修改代码?**
**3. 当前的事件注册方式是否线程安全?为什么?应如何改进以确保线程安全?**
---
### 🎯 难点解析(供面试官参考):
1. **闭包捕获陷阱**:`counter` 是在循环中定义的局部变量,被多个 Task 中的 lambda 表达式捕获。由于 Task 是异步执行的,当它们真正执行时,`counter` 的值可能已经改变,导致所有 Task 都捕获到相同的最终值(例如 4)。
2. **异步与事件注册时机**:由于事件注册发生在异步 Task 中,而 `RaiseEvent` 是在主线程中立即调用的,可能导致事件尚未注册就被触发,从而部分或全部事件监听器未生效。
3. **线程安全问题**:多线程中使用 `+=` 操作符来注册事件是非原子操作,可能导致竞态条件。`event` 使用默认的 `EventHandler` 实现时,虽然 `+=` 和 `-=` 是线程安全的(使用了 `Interlocked.CompareExchange`),但在某些自定义事件实现中可能不是线程安全的。
---
### ✅ 理想回答要点:
- 正确识别闭包捕获变量的陷阱,并提出在循环体内复制变量(如 `int copy = counter;`)以确保每个 lambda 捕获不同的变量副本。
- 提出使用 `await Task.Run(...)` 替代 `Task.Run(...)` 并等待所有任务完成,以确保事件注册完成后再触发事件。
- 提出使用 `lock` 或者使用 `ConcurrentDictionary` 等方式来线程安全地管理事件订阅。
- 对 C# 中的闭包机制、异步编程模型(async/await)、事件线程安全等有深入理解。
---
### 💡 延伸讨论(可选):
- 如何使用 `WeakReference` 来避免内存泄漏?
- 如何设计一个线程安全的事件聚合器(Event Aggregator)?
- 在 .NET Core/.NET 5+ 中,`event` 的线程安全性是否有变化?
- 如何使用 `IProgress
---
如果你需要,我也可以提供这道题的标准答案和优化后的代码实现。是否需要?
除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
标签:AI写作
精华推荐