一个有趣的Unity多线程练习例子
Posted stalendp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个有趣的Unity多线程练习例子相关的知识,希望对你有一定的参考价值。
这里将记录一些C#线程相关的信息,将持续更新,最总将形成一个工具类,方便在Unity中使用。
一些Tips:
1)Monitor VS. Mutex
Difference between mutex and monitor.
The biggest difference between the two is the scope: a mutex's scope is system-wide, whilst monitor's scope depends on the scope of the object you lock on. This means that with monitor, the widest scope you can get is generally application-wide.
So you would mostly use monitor to synchronise between threads running in an application, and use mutex to synchronise between different applications.
lock is just shortcut for Monitor.Enter with try + finally and Monitor.Exit. Use lock statement whenever it is enough - if you need something like TryEnter, you will have to use Monitor.
Semaphore.WaitOne/Release vs Monitor.Pulse/Wait
Monitor.Wait and Monitor.Pulse are fundamental synchronization mechanisms that can be used to form pretty much any other synchronization device including semaphores.
结论,使用Monitor能够达到多线程的目的,lock可以简化这个过程。
4)Terminating a thread cleanly
5) Should a return statement be inside or outside a lock?
At the IL level they are identical。
Only the current owner of the lock can signal a waiting object using Pulse.
The Pulse, PulseAll, and Wait methods must be invoked from within a synchronized block of code.
一个下载线程的例子:
private class DownloadFactory
#region ===== 线程共享资源 =====
private volatile bool isOpen;
private Queue<DownloadJob> workingJobs = new Queue<DownloadJob>();
private Queue<DownloadJob> finishedJobs = new Queue<DownloadJob>();
#endregion
const int WORK_NUM = 5; // 开启5个下载线程
private Thread[] workers; // 工作线程,负责下载
private Thread checker; // 检测线程, 用来写磁盘或则重新下载
public DownloadFactory()
isOpen = false;
workers = new Thread[WORK_NUM];
for (int i = 0; i < WORK_NUM; i++)
workers[i] = new Thread(() =>
while (isOpen)
var job = takeJob();
// do download affairs!
finJob(job);
);
checker = new Thread(() =>
while (isOpen)
var job = checkJob();
if (!job.isOK) // 下载不成功,则重新下载
putJob(job);
else // 写文件操作
);
public bool isDone
get
return totalCount == 0;
// 开始
public void Open(Queue<DownloadJob> _allJobs)
workingJobs = _allJobs;
isOpen = true;
checker.Start();
foreach (var w in workers)
w.Start();
// 结束
public void Close()
isOpen = false;
checker.Interrupt();
foreach (var w in workers)
w.Interrupt();
checker.Join();
foreach (var w in workers)
w.Join();
#region ========= 线程同步逻辑 ===========
private int totalCount
get
lock (this)
return workingJobs.Count + finishedJobs.Count;
private DownloadJob takeJob()
lock (this)
while (workingJobs.Count <= 0)
Monitor.Wait(this);
var rt = workingJobs.Dequeue();
Monitor.PulseAll(this);
return rt;
private void putJob(DownloadJob job)
lock (this)
workingJobs.Enqueue(job);
Monitor.PulseAll(this);
private void finJob(DownloadJob job)
lock (this)
finishedJobs.Enqueue(job);
Monitor.PulseAll(this);
private DownloadJob checkJob()
lock(this)
while (finishedJobs.Count <= 0)
Monitor.Wait(this);
var job = finishedJobs.Dequeue();
Monitor.PulseAll(this);
return job;
#endregion
例子:生产消费者模式
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine.UI;
public class MultiThread : MonoBehaviour
private List<ThreadHelper> threads = new List<ThreadHelper>();
public Text txtMessage;
public Text txtMsg2;
void Start()
var kitchen = new Kitchen();
var table = new Table(10);
var msgHandler = new TMsgHandler(this, msg =>
txtMsg2.text = msg as string;
);
ThreadHelper.Run eater = env =>
try
var random = new System.Random((int)env.param);
while (env.alive)
var cake = table.take();
Thread.Sleep(random.Next(100));
msgHandler.send(delegate
txtMessage.text = env.name + "" + cake;
);
catch (ThreadInterruptedException ex)
msgHandler.send(delegate
txtMessage.text = string.Format("The 0 Interrupted!", env.name);
);
finally
msgHandler.send(delegate
txtMessage.text = string.Format("The 0 Ended!", env.name);
);
;
ThreadHelper.Run maker = env =>
try
var param = env.param as object[];
var random = new System.Random((int)param[1]);
while (env.alive)
Thread.Sleep(random.Next(100));
var cake = string.Format("[ Cake No.0 by 1 ]", kitchen.nextCakeId, env.name);
table.put(cake);
msgHandler.send(env.name + "" + cake);
catch (ThreadInterruptedException ex)
msgHandler.send(delegate
txtMessage.text = string.Format("The 0 Interrupted!", env.name);
);
finally
msgHandler.send(delegate
txtMessage.text = string.Format("The 0 Ended!", env.name);
);
;
// 一些随机数种子:13821, 97535, 79593, 87481, 66158, 18199, 40915, 63093, 77880, 93602, 17603, 38327
threads.Add(new ThreadHelper("MakerThread-1", maker, new object[] "111", 13821 ));
threads.Add(new ThreadHelper("MakerThread-2", maker, new object[] "222", 97535 ));
threads.Add(new ThreadHelper("MakerThread-3", maker, new object[] "111", 79593 ));
threads.Add(new ThreadHelper("MakerThread-4", maker, new object[] "222", 87481 ));
threads.Add(new ThreadHelper("MakerThread-5", maker, new object[] "111", 66158 ));
threads.Add(new ThreadHelper("MakerThread-6", maker, new object[] "222", 18199 ));
threads.Add(new ThreadHelper("EaterThread-A", eater, 40915));
threads.Add(new ThreadHelper("EaterThread-B", eater, 63093));
threads.Add(new ThreadHelper("EaterThread-C", eater, 77880));
threads.Add(new ThreadHelper("EaterThread-D", eater, 93602));
threads.Add(new ThreadHelper("EaterThread-E", eater, 17603));
threads.Add(new ThreadHelper("EaterThread-F", eater, 38327));
// 开始线程
threads.ForEach(t => t.Start());
void OnDestroy()
threads.ForEach(m => m.Kill());
// 同步消息到主线程,模拟android.os.Handler的概念
public class TMsgHandler
public delegate void PostHandler();
private System.Action<object> msgHandler;
private List<object> messages = new List<object>();
public TMsgHandler(MonoBehaviour mono, System.Action<object> handler = null)
msgHandler = handler;
mono.StartCoroutine(checkMessage());
public void send(PostHandler action)
lock (this)
messages.Add(action);
public void send(object param)
lock (this)
messages.Add(param);
private List<object> getMessages()
lock (this)
if (messages.Count > 0)
var old = messages;
messages = new List<object>();
return old;
return null;
private IEnumerator checkMessage()
while (true)
yield return null;
var msgs = getMessages();
if (msgs != null)
Debug.LogError(msgs.Count);
foreach (var m in msgs)
if (m is PostHandler)
var h = m as PostHandler;
h();
else
if (msgHandler != null)
msgHandler(m);
public class ThreadHelper
public delegate void Run(ThreadHelper env);
public Run run = null;
public volatile bool alive;
public string name get return thread.Name;
public Thread thread get; private set;
public object param = null;
public ThreadHelper(string name, Run _run, object _param = null)
alive = false;
run = _run;
param = _param;
thread = new Thread(_inner_run);
thread.Name = name;
//background threads do not prevent a process from terminating.
//Once all foreground threads belonging to a process have terminated,
//the common language runtime ends the process.
thread.IsBackground = true;
public void _inner_run()
if (run != null)
run(this);
public void Start()
alive = true;
thread.Start();
public void notifyStop()
alive = false;
public void Kill(int timeout = 0)
thread.Interrupt();
thread.Join(timeout);
public class Kitchen
private int CakeId;
public int nextCakeId
get
lock (this)
return CakeId++;
public class Table
private string[] buffer;
private int tail;
private int head;
private int count;
public Table(int _count)
this.buffer = new string[_count];
tail = head = count = 0;
public void put(string cake)
lock (this)
//Debug.LogWarningFormat("0 >> puts >> 1", Thread.CurrentThread.Name, cake);
while (count >= buffer.Length)
//Debug.LogFormat("0 wait BEGIN", Thread.CurrentThread.Name);
Monitor.Wait(this);
//Debug.LogFormat("0 wait END", Thread.CurrentThread.Name);
buffer[tail] = cake;
tail = (tail + 1) % buffer.Length;
count++;
Monitor.Pulse(this);
public string take()
lock (this)
while (count <= 0)
//Debug.LogFormat("0 wait BEGIN", Thread.CurrentThread.Name);
Monitor.Wait(this);
//Debug.LogFormat("0 wait END", Thread.CurrentThread.Name);
var cake = buffer[head];
head = (head + 1) % buffer.Length;
count--;
Monitor.Pulse(this);
//Debug.LogErrorFormat("0 << takes << 1", Thread.CurrentThread.Name, cake);
return cake;
更加有趣的一个版本:
using UnityEngine;
using System.Collections;
using System.Threading;
using System.Collections.Generic;
// 一个线程辅助类
public class ThreadObj
protected System.Action<ThreadObj> run = null;
public volatile bool alive;
public string thdName get return thread.Name;
public Thread thread get; private set;
public object param = null;
public ThreadObj(string name, System.Action<ThreadObj> _run = null, object _param = null)
alive = false;
run = _run;
param = _param;
thread = new Thread(_inner_run);
thread.Name = name;
//background threads do not prevent a process from terminating.
//Once all foreground threads belonging to a process have terminated,
//the common language runtime ends the process.
thread.IsBackground = true;
public void _inner_run()
if (run != null)
run(this);
public void Start()
alive = true;
thread.Start();
public void notifyStop()
alive = false;
public void Kill(int timeout = 0)
thread.Interrupt();
thread.Join(timeout);
// 同步消息到主线程,模拟android.os.Handler的概念
public class TMsgHandler
public delegate void PostHandler();
private System.Action<object> msgHandler;
private List<object> messages = new List<object>();
public TMsgHandler(MonoBehaviour mono, System.Action<object> handler = null)
msgHandler = handler;
mono.StartCoroutine(checkMessage());
public void send(object param)
lock (this)
messages.Add(param);
private List<object> getMessages()
lock (this)
if (messages.Count > 0)
var old = messages;
messages = new List<object>();
return old;
return null;
private IEnumerator checkMessage()
while (true)
yield return null;
var msgs = getMessages();
if (msgs != null)
foreach (var m in msgs)
if (m is PostHandler)
var h = m as PostHandler;
h();
else
if (msgHandler != null)
msgHandler(m);
using UnityEngine;
using UnityEngine.UI;
// 餐厅表现
public class UIDiningHall : MonoBehaviour
public Text cakeNo;
public Table table;
public Transform makers;
public Transform eaters;
private DiningHall diningHall = new DiningHall();
// Use this for initialization
void Start ()
var msgHandler = new TMsgHandler(this, msg =>
if (msg is Maker.Event) // 厨师消息
Debug.Log(msg.ToString());
else if (msg is Eater.Event) // 食客消息
Debug.Log(msg.ToString());
);
diningHall.Init(msgHandler);
diningHall.Open(); // 餐厅营业
void OnDestroy()
Debug.LogError("=========Dining Hall is Closing===========");
diningHall.Close(); // 餐厅打烊
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
// 餐厅, 逻辑运行
public class DiningHall
private List<ThreadObj> persons = new List<ThreadObj>(); // 餐厅中的人,包括厨师和食客
public Kitchen kitchen get; private set;
public Table table get; private set;
private TMsgHandler handler;
public void Init(TMsgHandler msgHandler)
handler = msgHandler;
kitchen = new Kitchen(30);
table = new Table(5);
// 一些随机数种子:13821, 97535, 79593, 87481, 66158, 18199,
// 40915, 63093, 77880, 93602, 17603, 38327
// 创建一些厨师
persons.Add(new Maker("CookA", 1, 13821, this));
persons.Add(new Maker("CookB", 1, 97535, this));
persons.Add(new Maker("CookC", 1, 79593, this));
persons.Add(new Maker("CookD", 1, 87481, this));
persons.Add(new Maker("CookE", 1, 87481, this));
persons.Add(new Maker("CookF", 1, 87481, this));
persons.Add(new Maker("CookG", 1, 87481, this));
persons.Add(new Maker("CookA", 1, 13821, this));
// 创建一些食客
persons.Add(new Eater("Eater1", 10, 40915, this));
persons.Add(new Eater("Eater2", 20, 63093, this));
persons.Add(new Eater("Eater3", 30, 77880, this));
persons.Add(new Eater("Eater4", 40, 93602, this));
persons.Add(new Eater("Eater1", 10, 40915, this));
persons.Add(new Eater("Eater2", 20, 63093, this));
persons.Add(new Eater("Eater3", 30, 77880, this));
persons.Add(new Eater("Eater4", 40, 93602, this));;
// 发送信息到UI
public void send2UI(object param)
handler.send(param);
public Cake takeCake()
return table.take();
// 开始营业
public void Open()
// 开始线程, 让餐厅运作起来
persons.ForEach(t => t.Start());
// 结束营业
public void Close()
persons.ForEach(t => t.Kill());
class Eater : ThreadObj
public string name get; private set; // 食客的名字
public int speed get; private set; // 食客吃蛋糕的速度
private System.Random rand;
private DiningHall dh;
public Eater(string name, int speed, int seed, DiningHall dh) : base(name)
this.name = name;
this.speed = speed;
this.rand = new System.Random(seed);
this.dh = dh;
this.run = Run;
// 食客在餐厅中取餐并吃~~
private void Run(ThreadObj env)
try
while (env.alive)
// 获取蛋糕
Event.takeStart(this);
var cake = dh.takeCake();
// 开始吃蛋糕, 直至吃完
while (cake.percent > 0)
cake.eat(rand.Next(speed)); // 计算一口的大小
Event.eatStart(this, cake);
Thread.Sleep(100); // 每吃一口,要咀嚼一下
Debug.LogWarning("[Eater] " + cake.ToString());
// 蛋糕吃完了
Event.eatEnd(this);
catch (ThreadInterruptedException ex)
finally
// 食客消息, 和UI通信的消息
public class Event
public int id get; private set;
public int type get; private set;
public Cake cake get; private set;
public static void takeStart(Eater eater)
eater.dh.send2UI(new Event() type = 0 );
public static void eatStart(Eater eater, Cake _cake)
eater.dh.send2UI(new Event() type = 1, cake = _cake.clone() );
public static void eatEnd(Eater eater)
eater.dh.send2UI(new Event() type = 2 );
public override string ToString()
return string.Format("[Eater] type: 0, cake: 1", type, cake == null ? "-" : cake.ToString());
// 厨师
public class Maker : ThreadObj
public string name get; private set; // 厨师的名字
public float speed get; private set; // 厨师制作蛋糕的速度
private System.Random rand;
private DiningHall dh;
public Maker(string name, float speed, int seed, DiningHall dh) : base(name)
this.name = name;
this.speed = speed;
this.rand = new System.Random(seed);
this.dh = dh;
this.run = Run;
// 厨师工作中~~
private void Run(ThreadObj env)
try
while (env.alive)
// 0. 获取蛋糕机的使用
Event.waitMachine(this);
var machine = dh.kitchen.getMachine(); // 获取蛋糕机
// 1. 设计一个蛋糕,并让面包机做
//dh.send2UI(Event.makeStart(cake)); // 开始制作
var type = rand.Next(5);
var amount = rand.Next(50, 200);
var cake = new Cake(type, amount);
machine.Cook(cake); // 制作蛋糕中~~
Event.waitCooking(this, cake);
cake = machine.GetCake(); //得到蛋糕
// 2. 放置蛋糕
Event.putCake(this);
dh.table.put(cake);
Event.putCakeEnd(this);
catch (ThreadInterruptedException ex)
finally
public class Event
public int type get; private set;
public Cake cake get; private set;
public static void waitMachine(Maker m)
m.dh.send2UI(new Event() type = 0 );
public static void waitCooking(Maker m, Cake c) // 等待
m.dh.send2UI(new Event() type = 1, cake = c.clone() );
public static void putCake(Maker m)
m.dh.send2UI(new Event() type = 2 );
public static void putCakeEnd(Maker m)
m.dh.send2UI(new Event() type = 3 );
public override string ToString()
return string.Format("[Maker] type: 0, cake: 1", type, cake==null ? "-" : cake.ToString());
// 蛋糕
public class Cake
static int ID = 0;
private int _id = 0;
public int type get; private set; // 蛋糕类型
public float totalAmount get; private set; // 蛋糕的设计尺寸
public float curAmount get; private set; // 当前蛋糕的尺寸
private int status; // 0 表示制作中, 1表示制作完成; -1 表示状态不可变(由于UI显示)
public float percent get return curAmount / (float)totalAmount;
public Cake(int type, float totalAmount, int id=-1, float curAmount =0, int status = 0)
this.type = type;
this.totalAmount = totalAmount;
this.curAmount = curAmount;
this.status = status;
_id = id < 0 ? ++ID : id;
public int id
get
return _id;
// 制作蛋糕
public void make(float amount)
if (status == 0)
curAmount += amount;
if (curAmount >= totalAmount)
curAmount = totalAmount;
status = 1;
// 吃蛋糕
public void eat(int amount)
if (status == 1)
curAmount -= amount;
if (curAmount <= 0)
curAmount = 0;
status = -1;
public Cake clone()
return new Cake(type, totalAmount, id, curAmount, status) ;
public override string ToString()
return string.Format(" id: 0, type: 1, total: 2:0.0, cur: 3:0.0, status: 4, percent: 5:0.0 ",
id, type, totalAmount, curAmount, status, percent);
// 蛋糕机
public class CakeMachine
public Kitchen kitchen get; private set;
private Cake curCake = null;
private bool isCooking = false;
public CakeMachine(string name, Kitchen kitchen)
this.kitchen = kitchen;
private void Run()
if (curCake != null)
while (curCake.percent < 1)
curCake.make(10);
Thread.Sleep(100); // 制作时间
Debug.LogError("Done!" + curCake.id );
MarkAsDone();
// 开始做蛋糕
public void Cook(Cake cake)
lock (this)
while (isCooking)
Monitor.Wait(this);
curCake = cake;
isCooking = true;
Debug.LogWarning("[Cook] " + curCake.ToString());
var thread = new Thread(Run);
thread.IsBackground = true;
thread.Start();
// 拿出蛋糕
public Cake GetCake()
lock(this)
while (isCooking)
Monitor.Wait(this);
return curCake.clone();
// 手动结束
public void ShutDown()
lock (this)
//Kill();
MarkAsDone();
// 蛋糕做好了
public void MarkAsDone()
lock (this)
isCooking = false;
Monitor.PulseAll(this);
kitchen.MachineReady();
public bool isDone
get
lock (this)
return !isCooking;
// 厨房, 里面有蛋糕机
public class Kitchen
private List<CakeMachine> machines; // 蛋糕机
public Kitchen(int machineNum)
machines = new List<CakeMachine>();
for (int i = 0; i < machineNum; i++)
machines.Add(new CakeMachine("M" + i, this));
public CakeMachine getMachine() // 尝试获取一台可用的机器
lock (this)
CakeMachine free = null;
while (free == null)
free = machines.Find(m => m.isDone);
if (free == null) // 没有机器是空闲的,需要等待
Monitor.Wait(this);
return free;
public void MachineReady() // 某台机器用好了
lock (this)
Monitor.PulseAll(this);
// 定义桌子,注意这个是共享资源!!厨师和食客共享
public class Table
private Cake[] buffer;
private int tail;
private int head;
private int count;
public Table(int _count)
this.buffer = new Cake[_count];
tail = head = count = 0;
public void put(Cake cake)
lock (this)
while (count >= buffer.Length)
Monitor.Wait(this);
buffer[tail] = cake;
tail = (tail + 1) % buffer.Length;
count++;
Monitor.Pulse(this);
public Cake take()
lock (this)
while (count <= 0)
Monitor.Wait(this);
var cake = buffer[head];
head = (head + 1) % buffer.Length;
count--;
Monitor.Pulse(this);
return cake;
以上是关于一个有趣的Unity多线程练习例子的主要内容,如果未能解决你的问题,请参考以下文章