C# 当主页按钮按下向下或返回主菜单时取消协同程序
我正在做的事情的一些借口;我目前通过在协同程序中设置interactiable=false来锁定我的技能按钮。通过textmeshpro显示还押秒的文本,并在倒计时结束时将其设置为非活动状态。但是,当按下主页按钮/返回主菜单时,我遇到了问题。我想刷新我的按钮冷却和停止协同程序时,按下它。但它仍处于锁定状态 这是我的冷却协同程序C# 当主页按钮按下向下或返回主菜单时取消协同程序,c#,unity3d,time,countdown,coroutine,C#,Unity3d,Time,Countdown,Coroutine,我正在做的事情的一些借口;我目前通过在协同程序中设置interactiable=false来锁定我的技能按钮。通过textmeshpro显示还押秒的文本,并在倒计时结束时将其设置为非活动状态。但是,当按下主页按钮/返回主菜单时,我遇到了问题。我想刷新我的按钮冷却和停止协同程序时,按下它。但它仍处于锁定状态 这是我的冷却协同程序 static List<CancellationToken> cancelTokens = new List<CancellationToken>
static List<CancellationToken> cancelTokens = new List<CancellationToken>();
...
public IEnumerator StartCountdown(float countdownValue, CancellationToken cancellationToken)
{
try
{
this.currentCooldownDuration = countdownValue;
// Deactivate myButton
this.myButton.interactable = false;
//activate text to show remaining cooldown seconds
this.m_Text.SetActive(true);
while (this.currentCooldownDuration > 0 && !cancellationToken.IsCancellationRequested)
{
this.m_Text.GetComponent<TMPro.TextMeshProUGUI>().text = this.currentCooldownDuration.ToString(); //Showing the Score on the Canvas
yield return new WaitForSeconds(1.0f);
this.currentCooldownDuration--;
}
}
finally
{
// deactivate text and Reactivate myButton
// deactivate text
this.m_Text.SetActive(false);
// Reactivate myButton
this.myButton.interactable = true;
}
}
这是当按下主页按钮/返回主菜单时我捕捉的地方。当你接住它,突然停下来
public void PauseGame()
{
GameObject menu = Instantiate(PauseMenu);
menu.transform.SetParent(Canvas.transform, false);
gameManager.PauseGame();
EventManager.StartListening("ReturnMainMenu", (e) =>
{
Cooldown.cancelAllCoroutines();
Destroy(menu);
BackToMainMenu();
EventManager.StopListening("ReturnMainMenu");
});
...
我在游戏暂停时也会停下来
public void PauseGame() {
Time.timeScale = 0.0001f;
}
Unity有
StopCoroutine()
专门用于提前结束协同路由
您将希望创建一个函数,当您希望按如下方式重置所有技能按钮对象时,可以调用该函数:
public void OnButtonClick()
{
// You should probably call `cancelAllCoroutines()` here
cancelAllCoroutines();
var cancellationTokenSource = new CancellationTokenSource();
cancelTokens.Add(cancellationTokenSource);
Coroutine co = StartCoroutine(StartCountdown(cooldownDuration, cancellationTokenSource.Token));
myCoroutines.Add(co);
}
void resetButton()
{
StopCorroutine(开始计数);
此.currentCooldownDuration=0;
此.m_Text.SetActive(false);
this.myButton.interactiable=true;
}
在这种情况下,您不正确地使用了CancellationToken
CancellationToken
是一种结构,它将CancellationTokenSource
包装为如下所示:
public bool IsCancellationRequested
{
get
{
return source != null && source.IsCancellationRequested;
}
}
因为它是一个结构,所以会按值传递,这意味着存储在列表中的实例与协同程序的实例不同
处理取消的典型方法是创建一个标记并传递其标记。无论何时,只要在CancellationTokenSource
上调用.cancel()
方法即可取消。采用这种方式的原因是,CancellationToken
只能通过“源”引用而不是令牌的消费者来取消
在您的情况下,您正在创建一个完全没有源的令牌,因此我建议进行以下更改:
首先,将您的cancelTokens
列表更改为:
List<CancellationTokenSource>
最后,将cancelAllCoroutines()
方法更改为:
public static void CancelAllCoroutines()
{
Debug.Log("cancelling all coroutines with total of : " + cancelTokens.Count);
foreach (CancellationTokenSource ca in cancelTokens)
{
ca.Cancel();
}
// Clear the list as @Jack Mariani mentioned
cancelTokens.Clear();
}
我建议您阅读网站上的文档,或者按照@JLum的建议,使用Unity提供的方法
编辑:
我忘了提到,建议在不再使用时弃置CancallationTokenSources
,以确保不会发生内存泄漏。我建议对你的monobhavior
这样做:
private void OnDestroy()
{
foreach(var source in cancelTokens)
{
source.Dispose();
}
}
编辑2:
正如@Jack Mariani在他的回答中提到的,在这种情况下,多个CancellationTokenSources
是过分的。它真正允许您做的就是对取消的Coroutine
进行更细粒度的控制。在这种情况下,您将一次性取消它们,因此,是的,优化将只创建其中一个。这里可以进行多种优化,但它们超出了这个问题的范围。我没有把它们包括在内,因为我觉得这会让这个答案显得过于夸张
然而,我会争论他的观点,即CancellationToken
主要用于任务。直接从MSDN中的前几行开始:
从.NET Framework 4开始,.NET Framework使用统一的模型协同取消异步或长时间运行的同步操作。此模型基于称为取消令牌的轻量级对象
CancellationTokens
是轻量级对象。在大多数情况下,它们只是引用CancellationTokenSource
的简单结构。在他的回答中提到的“开销”可以忽略不计,在我看来,在考虑可读性和意图时完全值得
您可以通过索引传递大量的布尔值
,或者使用字符串
文本订阅事件,这些方法都可以工作。
但代价是什么?混乱且难以阅读的代码?我认为这不值得
但最终选择权归你。主要问题
在您的问题中,您只使用boolcancellationToken.IsCancellationRequested
,而不使用取消令牌的其他功能
因此,按照方法的逻辑,您可能只希望有一个列表cancellationRequests
,并使用传递它们
尽管如此,我还是不同意这种逻辑,也不同意达伦·鲁昂(Darren Ruane)提出的逻辑,因为他们有一个主要缺陷
缺陷=>这些解决方案不断向两个列表添加内容取消令牌。添加(…)
和mycourrents.Add(co)
,但从未清除它们
如果你想这样做,你可以手动删除它们,但这很棘手,因为你永远不知道什么时候需要它们(在CancelAllCoroutines
method之后可以调用多个帧)
解决方案
使用静态事件而不是列表
要删除列表并使类更加解耦,可以使用静态事件,该事件在调用PauseGame
方法的脚本中创建和调用
//the event somewhere in the script
public static event Action OnCancelCooldowns;
public void PauseGame()
{
...your code here, with no changes...
EventManager.StartListening("ReturnMainMenu", (e) =>
{
//---> Removed ->Cooldown.cancelAllCoroutines();
//replaced by the event
OnCancelCooldowns?.Invoke();
...your code here, with no changes...
});
...
您将在协同程序中收听静态事件
public IEnumerator StartCountdown(float countdownValue, CancellationToken cancellationToken)
{
try
{
bool wantToStop = false;
//change YourGameManager with the name of the script with your PauseGame method.
YourGameManager.OnCancelCooldowns += () => wantToStop = true;
...your code here, with no changes...
while (this.currentCooldownDuration > 0 && !wantToStop)
{
...your code here, with no changes...
}
}
finally
{
...your code here, with no changes...
}
}
更简单的解决方案
或者你可能只是保持简单,使用而不是统一的(他们也更多)
提供完全解决问题的标记功能
单一协同程序启动
void OnButtonClick() => Timing.RunCoroutine(CooldownCoroutine, "CooldownTag");
停止所有带有特定标记的协同程序
public void PauseGame()
{
...your code here, with no changes...
EventManager.StartListening("ReturnMainMenu", (e) =>
{
//---> Removed ->Cooldown.cancelAllCoroutines();
//replaced by this
Timing.KillCoroutines("CooldownTag");
...your code here, with no changes...
});
...
进一步(请考虑MEC FROM只具有标签而不是层,但您需要在您的用例中只使用标签)。< /P>
编辑:
经过一番思考后,我决定删除bool解决方案的细节,这可能会混淆答案,并且超出了这个问题的范围。我的合作计划包含的不仅仅是我希望运行的技能。那么StopCor
public IEnumerator StartCountdown(float countdownValue, CancellationToken cancellationToken)
{
try
{
bool wantToStop = false;
//change YourGameManager with the name of the script with your PauseGame method.
YourGameManager.OnCancelCooldowns += () => wantToStop = true;
...your code here, with no changes...
while (this.currentCooldownDuration > 0 && !wantToStop)
{
...your code here, with no changes...
}
}
finally
{
...your code here, with no changes...
}
}
void OnButtonClick() => Timing.RunCoroutine(CooldownCoroutine, "CooldownTag");
public void PauseGame()
{
...your code here, with no changes...
EventManager.StartListening("ReturnMainMenu", (e) =>
{
//---> Removed ->Cooldown.cancelAllCoroutines();
//replaced by this
Timing.KillCoroutines("CooldownTag");
...your code here, with no changes...
});
...