Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/unity3d/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 当主页按钮按下向下或返回主菜单时取消协同程序_C#_Unity3d_Time_Countdown_Coroutine - Fatal编程技术网

C# 当主页按钮按下向下或返回主菜单时取消协同程序

C# 当主页按钮按下向下或返回主菜单时取消协同程序,c#,unity3d,time,countdown,coroutine,C#,Unity3d,Time,Countdown,Coroutine,我正在做的事情的一些借口;我目前通过在协同程序中设置interactiable=false来锁定我的技能按钮。通过textmeshpro显示还押秒的文本,并在倒计时结束时将其设置为非活动状态。但是,当按下主页按钮/返回主菜单时,我遇到了问题。我想刷新我的按钮冷却和停止协同程序时,按下它。但它仍处于锁定状态 这是我的冷却协同程序 static List<CancellationToken> cancelTokens = new List<CancellationToken>

我正在做的事情的一些借口;我目前通过在协同程序中设置interactiable=false来锁定我的技能按钮。通过textmeshpro显示还押秒的文本,并在倒计时结束时将其设置为非活动状态。但是,当按下主页按钮/返回主菜单时,我遇到了问题。我想刷新我的按钮冷却和停止协同程序时,按下它。但它仍处于锁定状态

这是我的冷却协同程序

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
的简单结构。在他的回答中提到的“开销”可以忽略不计,在我看来,在考虑可读性和意图时完全值得

您可以通过索引传递大量的
布尔值
,或者使用
字符串
文本订阅事件,这些方法都可以工作。 但代价是什么?混乱且难以阅读的代码?我认为这不值得

但最终选择权归你。

主要问题 在您的问题中,您只使用bool
cancellationToken.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...
    });
    ...