Unity里面的協(xié)程好用,但總是在如何關閉指定協(xié)程,尤其是關閉帶參數的協(xié)程的問題上困惑不已。
在本文,筆者帶你用最簡單的方式(2個方案)管理這些運行中的協(xié)程,做到點對點關閉,即便是帶參數協(xié)程,也能點對點關閉。
思路:
- 要每一個協(xié)程都在開啟后能再次拿到,并且具備辨識度,就需要緩存引用并為其配特定名稱(key),構成 一個 KeyValuePair ,一個個 KeyValuePair 的集合不就是字典?
- 所以想要管理協(xié)程,做好他們的增刪改查,這兒引入 Dictionary 保存協(xié)程 Coroutine 的引用 或者 干脆保存這個迭代器 IEnumerator ,如果想要關閉指定的協(xié)程,只需從字典里面拿到這些個引用,然后使用
StopCoroutineAPI 即可。 - 如果字典里面存的是 迭代器 IEnumerator , 那樣似乎更加棒棒,嗯還可以 抽取指定協(xié)程 使用
StartCoroutineAPI 開啟。
方案一:
執(zhí)行 StartCoroutine,將返回這個協(xié)同程序 Coroutine 的引用,將這個引用存到字典,需要的時候直接StopCoroutine(這個引用);
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestIEnumlator01 : MonoBehaviour {
//方案一,執(zhí)行 StartCoroutine,將返回 Coroutine 引用,需要的時候直接 StopCoroutine(這個引用);
Dictionary<string, Coroutine> CoroutineDic = new Dictionary<string, Coroutine>();
//消息輸出字典,用于Debug
Dictionary<string, string> MessageDic = new Dictionary<string, string>() {
{ "a", "Test04" },
{ "s", "Test05" },
{ "d", "Test06" }
};
void Start () {
//模擬運行協(xié)程
CoroutineDic.Add("a", StartCoroutine(Test04()));
CoroutineDic.Add("s", StartCoroutine(Test05(1f)));
CoroutineDic.Add("d", StartCoroutine(Test06("測試", 1f)));
}
private void Update()
{
if (Input.anyKeyDown)
{
string name = Input.inputString;
if (CoroutineDic.ContainsKey(name))
{
StopCoroutine(CoroutineDic[name]);
CoroutineDic.Remove(name);
Debug.Log(string.Format("我按下了{0}鍵,字典還有{1}個對象,注意看協(xié)程{2}還有輸出不?", name, CoroutineDic.Count, MessageDic[name]));
}
}
}
//以下協(xié)程均為測試用,為了便于觀察,均為死循環(huán)且定時均為1秒
IEnumerator Test04()//不帶參數協(xié)程
{
while (true)
{
yield return new WaitForSeconds(1f);
Debug.Log("我是Test04:"+Time.realtimeSinceStartup);
}
}
IEnumerator Test05(float _DelayTime)//帶一個參數的協(xié)程
{
while (true)
{
yield return new WaitForSeconds(_DelayTime);
Debug.Log("我是Test05:" + Time.realtimeSinceStartup);
}
}
IEnumerator Test06(string msg,float delayTime)//帶兩個參數的協(xié)程
{
while (true)
{
yield return new WaitForSeconds(delayTime);
Debug.Log("我是Test06:"+ Time.realtimeSinceStartup + "傳入字符串為:" + msg);
}
}
}
方案二:
將迭代器 IEnumerator(表象是存的這個協(xié)程方法名)先添加到字典,給它指定一個特定的名字, 想拿到指定的迭代器 IEnumerator,不就是 Dictionary.TryGetValue 么,有了迭代器的在手, 就可以隨意開啟 / 關閉 指定的協(xié)程啦;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestIEnumlator02 : MonoBehaviour {
//方案二,聲明字典,緩存迭代器,制作映射表。
Dictionary<string, IEnumerator> IEnumeratorDic = new Dictionary<string, IEnumerator>();
//消息輸出字典,用于Debug
Dictionary<string, string> MessageDic = new Dictionary<string, string>() {
{ "q", "Test01" },
{ "w", "Test02" },
{ "e", "Test03" }
};
void Start () {
//添加協(xié)程引用,并為其配置一個鍵 Key,方便后續(xù)增刪改查的操作
IEnumeratorDic.Add("q",Test01());
IEnumeratorDic.Add("w",Test02(1f));
IEnumeratorDic.Add("e",Test03("測試",1f));
//依次執(zhí)行這些協(xié)程
foreach (var item in IEnumeratorDic)
{
StartCoroutine(item.Value);
}
}
private void Update()
{
if (Input.anyKeyDown)
{
string name=Input.inputString;
if (IEnumeratorDic.ContainsKey(name))
{
StopCoroutine(IEnumeratorDic[name]);
IEnumeratorDic.Remove(name);
Debug.Log(string.Format("我按下了{0}鍵,字典還有{1}個對象,注意看協(xié)程{2}還有輸出不?", name, IEnumeratorDic.Count,MessageDic[name]));
}
}
}
//以下協(xié)程均為測試用,為了便于觀察,均為死循環(huán)且定時均為1秒
IEnumerator Test01()//不帶參數協(xié)程
{
while (true)
{
yield return new WaitForSeconds(1f);
Debug.Log("我是Test01:" + Time.realtimeSinceStartup);
}
}
IEnumerator Test02(float _DelayTime)//帶一個參數的協(xié)程
{
while (true)
{
yield return new WaitForSeconds(_DelayTime);
Debug.Log("我是Test02:" + Time.realtimeSinceStartup);
}
}
IEnumerator Test03(string msg, float delayTime)//帶兩個參數的協(xié)程
{
while (true)
{
yield return new WaitForSeconds(delayTime);
Debug.Log("我是Test03:" + Time.realtimeSinceStartup + "傳入字符串為:" + msg);
}
}
}
動畫:

總結:
原來,協(xié)程也可以這么好用的,想執(zhí)行誰,想停止誰,不管你帶沒帶參數,都可以單獨停止,單獨啟動。
想想,以前為了關閉一個帶參數的協(xié)程,不得不執(zhí)行 StopAllCoroutines()也是醉了;
引入 Dictionary 等數據結構,為這些協(xié)程指定一個有意義的key, 增刪改查手到擒來,取用起來也就更方便啦!
嗯,多研讀API,有更多驚喜~
小知識:
- 如果一個類里面協(xié)程有多個重載,且均只有一個參數,可以用如下的API啟動。
StartCoroutine("FuncName",1);
StartCoroutine("FuncName","參數");
StartCoroutine("FuncName",gameObject);
像tips1那樣使用字符串來開啟協(xié)程,有一個好處:就是使用StopCoroutine("FuncName")能夠停止所有的運行在這個實例上的有著這個方法名的協(xié)程。
相應的,用StartCoroutine(FuncName(1))這樣開啟的協(xié)程,不管有無參數,StopCoroutine("FuncName")都是不湊效的。
將Coroutine所在的腳本設為enable = false并不能夠將Coroutine停止,因為它跟Monobehavior是同層級的。
啟動一個協(xié)程時緩存他Coroutine類型的返回值是一個很好的習慣,這樣在啟動這個協(xié)程前判null就能保證這個協(xié)程不被多次加載,形如:
private Coroutine cor=null;
private void Start()
{
if(cor==null)
{
cor=StartCoroutine(TestFunc);
}else
{
Debug.Log("協(xié)程運行中");
}
}
- 苦惱協(xié)程沒有返回值 ,ref / out 也不行? 給個 Action 或者 Func 做參數就很巴適~ 我的這篇文字:Unity3D IEnumerator 做一個通用的定時器 就是這個理念了。
擴展閱讀
基于協(xié)程的TaskManager
協(xié)程管理CoroutineManager - 筆者整理的
標簽:unity 3d,IEnumerator 協(xié)程,StartCoroutine,StopCoroutine關閉協(xié)程,關閉指定協(xié)程,關閉指定的帶參數協(xié)程