在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接口,有更多需求的话自行封装,这里不再过多赘述。