1. 协程与事件:C#异步编程的双生花
在C#开发中,协程和事件就像一对默契的搭档,一个负责优雅地处理异步流程,一个负责灵活响应状态变化。我曾在工业控制项目中同时使用这两种机制,让上位机在毫秒级完成设备状态监控和异常处理。不同于教科书上的概念堆砌,这里我想分享几个真实场景中的组合用法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 协程在C#中的实现原理
2.1 yield return的工作机制
C#的协程通过迭代器模式实现,编译器会将包含yield return的方法转换为状态机。我曾用ILSpy反编译过这样的代码:
csharp复制IEnumerator SensorCheck()
{
while(true)
{
yield return new WaitForSeconds(1);
Debug.Log(ReadTemperature());
}
}
实际生成的类包含4个关键状态:-2(初始)、0(第一次yield)、1(第二次yield)、-1(结束)。这种实现方式比Unity的协程更底层,但原理相通。
2.2 与async/await的异同
虽然async/await也是异步编程模型,但协程更适合需要分帧执行的场景。比如在工业相机采集时:
- async/await:适合单次高延迟操作(如网络请求)
- 协程:适合持续性的轮询(如设备状态检测)
重要提示:在.NET 4.x环境中,混合使用协程和async可能导致死锁,建议统一用IEnumerator或者async/await。
3. 事件系统的深度应用
3.1 标准事件模式的问题
典型的event+delegate模式在工业控制中会遇到两个痛点:
- 高频事件导致UI线程阻塞(如相机每秒30帧回调)
- 多订阅者时的异常传播
改进方案是引入中间层:
csharp复制class EventHub
{
private static readonly ConcurrentDictionary<Type, List<Delegate>> _handlers = new();
public static void Subscribe<T>(Action<T> handler)
{
var handlers = _handlers.GetOrAdd(typeof(T), _ => new());
lock(handlers) { handlers.Add(handler); }
}
public static void Publish<T>(T eventData)
{
if(_handlers.TryGetValue(typeof(T), out var handlers))
{
foreach(var handler in handlers.ToArray())
{
Task.Run(() => handler.DynamicInvoke(eventData));
}
}
}
}
3.2 事件与Rx的结合
Reactive Extensions能解决事件流的组合问题。比如同时监控多个传感器:
csharp复制var tempObservable = Observable.FromEventPattern<TemperatureEventArgs>(
h => TemperatureSensor.ReadingChanged += h,
h => TemperatureSensor.ReadingChanged -= h);
var pressureObservable = Observable.FromEventPattern<PressureEventArgs>(
h => PressureSensor.ReadingChanged += h,
h => PressureSensor.ReadingChanged -= h);
tempObservable.Zip(pressureObservable, (t,p) => new{t,p})
.Throttle(TimeSpan.FromMilliseconds(500))
.Subscribe(data => UpdateDashboard(data.t, data.p));
4. 协程与事件的组合拳
4.1 设备控制典型场景
在固高运动控制卡开发中,需要:
- 协程持续发送控制指令
- 事件监听位置反馈
csharp复制IEnumerator MoveToPosition(float target)
{
var completionSource = new TaskCompletionSource<bool>();
EventHandler<PositionReachedEventArgs> handler = (s,e) => {
if(Math.Abs(e.Position - target) < 0.01)
completionSource.SetResult(true);
};
PositionReached += handler;
SendMoveCommand(target);
yield return new WaitUntil(() => completionSource.Task);
PositionReached -= handler;
Debug.Log("Movement completed");
}
4.2 异常处理策略
当协程中触发异常事件时,推荐使用CancellationTokenSource:
csharp复制IEnumerator ProcessWithTimeout(float timeout)
{
var cts = new CancellationTokenSource();
ErrorOccurred += (s,e) => cts.Cancel();
try
{
yield return SomeLongOperation().WithCancellation(cts.Token);
}
catch(OperationCanceledException)
{
yield return ShowErrorAlert("操作被中断");
}
}
5. 性能优化实战技巧
5.1 对象池减少GC压力
高频事件容易产生大量临时对象。对于工业相机的帧事件:
csharp复制class FrameDataPool
{
private static readonly ConcurrentQueue<FrameData> _pool = new();
public static FrameData Rent()
{
return _pool.TryDequeue(out var item) ? item : new FrameData();
}
public static void Return(FrameData item)
{
item.Reset();
_pool.Enqueue(item);
}
}
// 使用方式
Camera.NewFrame += (s,e) => {
var frame = FrameDataPool.Rent();
ProcessFrame(frame);
FrameDataPool.Return(frame);
};
5.2 协程调度优化
避免在Update中启动大量协程,改用自定义调度器:
csharp复制class CoroutineScheduler : MonoBehaviour
{
private readonly List<IEnumerator> _coroutines = new();
public void StartManagedCoroutine(IEnumerator routine)
{
_coroutines.Add(routine);
}
void Update()
{
for(int i = 0; i < _coroutines.Count; )
{
if(!_coroutines[i].MoveNext())
{
_coroutines.RemoveAt(i);
}
else
{
i++;
}
}
}
}
6. 调试与问题排查
6.1 协程堆栈跟踪
当协程挂起时,常规的堆栈信息会丢失。可以通过以下方法增强调试:
csharp复制class DebuggableCoroutine
{
public static IEnumerator Wrap(IEnumerator inner, [CallerMemberName] string caller = null)
{
while(true)
{
try
{
if(!inner.MoveNext()) yield break;
}
catch(Exception ex)
{
Debug.LogError($"[{caller}] Coroutine failed: {ex}");
throw;
}
yield return inner.Current;
}
}
}
// 使用方式
StartCoroutine(DebuggableCoroutine.Wrap(MyRoutine()));
6.2 事件泄漏检测
订阅事件容易导致内存泄漏,可以用弱引用包装:
csharp复制class WeakEvent<TEventArgs>
{
private readonly List<WeakReference<EventHandler<TEventArgs>>> _handlers = new();
public void AddHandler(EventHandler<TEventArgs> handler)
{
_handlers.Add(new WeakReference<EventHandler<TEventArgs>>(handler));
}
public void Invoke(object sender, TEventArgs e)
{
for(int i = _handlers.Count - 1; i >= 0; i--)
{
if(_handlers[i].TryGetTarget(out var handler))
{
handler(sender, e);
}
else
{
_handlers.RemoveAt(i);
}
}
}
}
7. 工业级应用案例
7.1 海康相机SDK集成
实际开发中发现海康相机SDK的事件回调在非UI线程:
csharp复制private void InitializeCamera()
{
_camera = new HCamera();
_camera.ConnectionChanged += (s,e) => {
if(e.IsConnected)
{
// 必须切换到UI线程更新控件
this.BeginInvoke(() => UpdateConnectionStatus(true));
// 启动采集协程
StartCoroutine(AcquisitionRoutine());
}
};
}
IEnumerator AcquisitionRoutine()
{
var buffer = FrameBufferPool.Rent();
while(_isCapturing)
{
if(_camera.GetFrame(buffer, 1000))
{
yield return ProcessFrame(buffer);
}
else
{
yield return null;
}
}
FrameBufferPool.Return(buffer);
}
7.2 MQTT通信实现
处理MQTT消息时的典型模式:
csharp复制class MqttService
{
public event EventHandler<MessageReceivedArgs> MessageReceived;
private IEnumerator _reconnectRoutine;
public void Connect()
{
_reconnectRoutine = ReconnectLoop();
StartCoroutine(_reconnectRoutine);
}
private IEnumerator ReconnectLoop()
{
while(true)
{
try
{
using(var client = new MqttClient())
{
client.Connected += OnConnected;
client.MessageReceived += OnMessage;
yield return ConnectClient(client);
// 保持连接状态
yield return new WaitUntil(() => !client.IsConnected);
}
}
catch(Exception ex)
{
Debug.LogError($"MQTT error: {ex}");
yield return new WaitForSeconds(5);
}
}
}
private void OnMessage(object sender, MessageEventArgs e)
{
MessageReceived?.Invoke(this, new MessageReceivedArgs(e.Topic, e.Message));
}
}
8. 高级模式与最佳实践
8.1 协程状态持久化
需要保存协程状态时,可以结合JSON序列化:
csharp复制[System.Serializable]
class CoroutineState
{
public string MethodName;
public object[] Parameters;
public int YieldPhase;
public IEnumerator Restore(MonoBehaviour runner)
{
var method = GetType().GetMethod(MethodName);
var routine = (IEnumerator)method.Invoke(this, Parameters);
for(int i = 0; i < YieldPhase; i++)
{
if(!routine.MoveNext()) yield break;
yield return routine.Current;
}
while(routine.MoveNext())
{
yield return routine.Current;
}
}
}
8.2 事件溯源模式
结合事件总线和协程实现复杂流程:
csharp复制class WorkflowEngine
{
private readonly Dictionary<string, IEnumerator> _workflows = new();
public void StartWorkflow(string id, IEnumerator routine)
{
if(_workflows.ContainsKey(id))
StopWorkflow(id);
_workflows[id] = routine;
EventBus.Subscribe<CancelEvent>(e => {
if(e.WorkflowId == id)
_workflows.Remove(id);
});
StartCoroutine(ExecuteWorkflow(id, routine));
}
private IEnumerator ExecuteWorkflow(string id, IEnumerator routine)
{
while(routine.MoveNext())
{
yield return routine.Current;
}
_workflows.Remove(id);
EventBus.Publish(new WorkflowCompleted(id));
}
}
