在C#多线程中实现任务处理队列
有什么不明白的地方,扫描右方二维码加我微信交流。
在看以下知识前,请先学习ManualResetEvent的使用。
多线程任务队列有什么用?
例如在Unity中,像一些耗时操作,如文件IO,网络请求,复杂计算等等,如果在主线程做这些任务,可能会阻塞主线程,这样就会导致游戏卡顿,掉帧。这个时候就要在非主线程来处理这些任务。
实现原理:
- 创建一个Thread跑一个Looper,就是一个死循环
- 使用ManualResetEvent类来控制Looper线程的阻塞和非阻塞
- 定义Hander类来向Looper中添加任务
- 当Looper中有任务时,ManualResetEvent调用Set方法不阻塞线程,Looper处理任务;当Looper中的所有任务处理完成后,ManualResetEvent调用Reset方法和WaitOne方法阻塞线程
具体的代码实现如下:
using System; using System.Collections.Generic; using System.Threading; using UnityEngine; public class LoopThread { public int ThreadId; private bool _isExiting; public Handler Handler; private Loop _loop; private Thread _thread; public void Start() { _isExiting = false; _loop = new Loop(); Handler = new Handler(_loop); _thread = new Thread(Run); _thread.Start(); } public void Exit() { _isExiting = true; _thread?.Abort(); } void Run() { ThreadId = Thread.CurrentThread.ManagedThreadId; while (!_isExiting) { _loop.Prepare(); while (true) { try { if (!_loop.Execute()) { break; } } catch (Exception e) { Debug.LogError("Loop failed, but continue"); Debug.LogException(e); } } } } } public class Loop { private readonly List<Action> _actionQueue; private readonly ManualResetEvent _manualResetEvent; public Loop() { _manualResetEvent = new ManualResetEvent(false); _actionQueue = new List<Action>(); } public void AddAction(Action action) { lock (_actionQueue) { _actionQueue.Add(action); } _manualResetEvent.Set(); } public void Prepare() { _manualResetEvent.WaitOne(); _manualResetEvent.Reset(); } public bool Execute() { Action callback; lock (_actionQueue) { if (_actionQueue.Count == 0) { return false; } callback = _actionQueue[0]; _actionQueue.RemoveAt(0); } try { callback(); } catch (Exception e) { Debug.LogError("Execute callback failed"); Debug.LogException(e); } return true; } } public class Handler { private readonly Loop _loop; public Handler(Loop loop) { _loop = loop; } public void Post(Action act) { _loop.AddAction(act); } }
以上为一个基类,可以实现其他类继承LoopThread。
例如我们要写一个文件IO的任务处理序列,则可以创建类FileIOLoopThread,继承LoopThread。然后简单封装一下Post接口,有更多需求的话自行封装,这里不再过多赘述。