首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >统一协同线的异常处理

统一协同线的异常处理
EN

Stack Overflow用户
提问于 2021-12-14 21:26:25
回答 2查看 2.3K关注 0票数 2

我创建了一个脚本来改变它所附加的GameObject的透明性,并且在一个衰落的协同线中进行透明性的更改,这需要被取消(并且每次我们用一个新的值调用ChangeTransparency()时都会取消)。我设法使它以我想要的方式工作,但我想处理正在淹没我的控制台的OperationCanceledException。我知道不能将yield return语句包装在try-catch块中。

什么是处理统一协同内异常的正确方法?

这是我的剧本:

代码语言:javascript
运行
复制
using System;
using System.Collections;
using System.Threading;
using UnityEngine;

public class Seethrough : MonoBehaviour
{
    private bool isTransparent = false;
    private Renderer componentRenderer;
    private CancellationTokenSource cts;
    private const string materialTransparencyProperty = "_Fade";


    private void Start()
    {
        cts = new CancellationTokenSource();

        componentRenderer = GetComponent<Renderer>();
    }

    public void ChangeTransparency(bool transparent)
    {
        //Avoid to set the same transparency twice
        if (this.isTransparent == transparent) return;

        //Set the new configuration
        this.isTransparent = transparent;

        cts?.Cancel();
        cts = new CancellationTokenSource();

        if (transparent)
        {
            StartCoroutine(FadeTransparency(0.4f, 0.6f, cts.Token));
        }
        else
        {
            StartCoroutine(FadeTransparency(1f, 0.5f, cts.Token));
        }
    }

    private IEnumerator FadeTransparency(float targetValue, float duration, CancellationToken token)
    {
        Material[] materials = componentRenderer.materials;
        foreach (Material material in materials)
        {
            float startValue = material.GetFloat(materialTransparencyProperty);
            float time = 0;

            while (time < duration)
            {
                token.ThrowIfCancellationRequested();  // I would like to handle this exception somehow while still canceling the coroutine

                material.SetFloat(materialTransparencyProperty, Mathf.Lerp(startValue, targetValue, time / duration));
                time += Time.deltaTime;
                yield return null;
            }

            material.SetFloat(materialTransparencyProperty, targetValue);
        }
    }
}

我的临时解决方案是检查令牌的取消标志并跳出while循环。虽然它解决了当前的问题,但我仍然需要一种方法来处理这些异常,这些异常是在Unity异步方法(coroutines)中抛出的。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-12-15 10:02:14

其他答案是好的,但实际上并不适合您试图解决的用例。

您没有任何异步代码,但是需要在Unity线程中执行代码,因为它使用的是Unity,而且大部分代码只能在主线程上使用。

柯鲁丁而不是ASYNC,与多线程或任务无关!

Coroutine基本上只是一个IEnumerator,只要StartCorotuine将它注册为Coroutine,那么Unity就会在Update之后调用MoveNext (它将执行所有事情直到下一个yield return语句),所以一旦在Update主线程上出现了一个框架(有一些特殊的产生语句,比如WaitForFixedUpdate等),这些语句会在不同的消息栈中处理,但仍然会被处理。

因此,例如。

代码语言:javascript
运行
复制
// Tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;

代码语言:javascript
运行
复制
// Will once a frame check if the provided time in seconds has already exceeded
// if so continues in the next frame, if not checks again in the next frame
yield return new WaitForSeconds(3f);

如果您真的需要对例程本身中的取消作出某种反应,那么除了立即抛出一个异常而只是检查CancellationToken.IsCancellationRequested并对其作出如下的反应,没有任何反对您的方法的话。

代码语言:javascript
运行
复制
while (time < duration)
{
    if(token.IsCancellationRequested)
    {
        // Handle the cancelation

        // exits the Coroutine / IEnumerator regardless of how deep it is nested in loops
        // not to confuse with only "break" which would only exit the while loop
        yield break;
    }
            
    ....

也要回答你的题目。确实,您不能在一个yield return块内执行try - catch,而且正如通常所说的那样,无论如何也没有真正的理由这样做。

如果确实需要,可以在yield return块中封装代码的某些部分,在try - catch块中不使用catch,在catch中执行错误处理,这样就可以再次使用yield break

代码语言:javascript
运行
复制
private IEnumerator SomeRoutine()
{
    while(true)
    {
        try
        {
            if(Random.Range(0, 10) == 9) throw new Exception ("Plop!");
        }
        catch(Exception e)
        {
            Debug.LogException(e);
            yield break;
        }

        yield return null;
    }
}
票数 1
EN

Stack Overflow用户

发布于 2021-12-15 05:36:13

这个答案就是https://stackoverflow.com/help/how-to-answer的一个例子。

编辑:,如果您不想使用Task,并且不太确定协同,为什么不在每个帧更新中使用一个简单的基于时间的插值函数。这是coroutines试图做的一个更干净的版本。

在统一的合作提供了一个简单的方式来执行一个长期的操作,在一系列的框架,但他们不是没有他们的问题。其中之一是关于他们从整个IEnumerator社区中明显奇怪地使用C#的激烈争论(搜索这个站点)。但可惜我离题了。

协同剂的替代品?

为什么不直接使用async/await呢?自从团结2018.1以来,一直完全支持 for .NET 4.5+和基于任务的异步模式(TAP)。一旦你这样做了,你会发现事情变得更容易了,你打开了一个更广泛的代码样本,你避开了讨厌的IEnumerator争论。

MSDN:

在联合中,异步编程通常使用协同实现。但是,自C# 5以来,异步编程在.NET开发中的首选方法是使用异步和等待关键字的基于任务的异步模式(TAP)。总之,在异步函数中,您可以等待任务完成,而无需阻止应用程序的其他部分更新.更多...

样品提供MSDN:

代码语言:javascript
运行
复制
// .NET 4.x async-await
using UnityEngine;
using System.Threading.Tasks;
public class AsyncAwaitExample : MonoBehaviour
{
    private async void Start()
    {
        Debug.Log("Wait.");
        await WaitOneSecondAsync();
        DoMoreStuff(); // Will not execute until WaitOneSecond has completed
    }
    private async Task WaitOneSecondAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        Debug.Log("Finished waiting.");
    }
}

异常处理

取消联合中的协同操作什么是处理统一协同中的异常的正确方法?

因此,这是合作关系的第一个问题之一。

答案是根本不使用协同机制(假设您运行的是Unity )并开始迁移到2018.1+。然后可以执行异常处理,例如:

代码语言:javascript
运行
复制
using UnityEngine;
using System.Threading.Tasks;
public class AsyncAwaitExample : MonoBehaviour
{
    CancellationTokenSource cts;

    private async void Start()
    {
        cts = new CancellationTokenSource ();
        Debug.Log("Wait.");

        try
        {
            await DoSomethingRisky(cts.Token);
        }
        catch (ApplicationException ex)
        {
            Debug.LogError("Terribly sorry but something bad happened");
        }

        DoMoreStuff(); // Will not execute until DoSomethingRisky has completed/faulted
    }

    private async Task DoSomethingRisky()
    {
        // ...something risky here

        throw new ApplicationException("Ouch!");
    }
}

现在,要处理特殊情况,如任务取消,您可以:

代码语言:javascript
运行
复制
public class AsyncAwaitExample : MonoBehaviour
{
    CancellationTokenSource cts;

    private async void Start()
    {
        cts = new CancellationTokenSource ();
        Debug.Log("Wait.");

        try
        {
            await PerformLengthyOperationAsync(cts.Token);
        }
        catch (OperationCanceledException ex) 
        {
            // task was cancelled
        }
        catch (ApplicationException ex)
        {
            Debug.LogError("Terribly sorry but something bad happened");
        }
    }

    private void Update ()
    {
        try
        {
            if (someCondition)
               cts.Cancel();
        }
        catch (OperationCanceledException ex) 
        {
            // Yup I know, I cancelled it above
        }
    }

    private async Task PerformLengthyOperationAsync(CancellationToken token)
    {
        var ok = true;

        do
        {
            token.ThrowIfCancellationRequested ();

            // continue to do something lengthy...
            // ok = still-more-work-to-do?
        }
        while (ok);
    }
}

另请参阅

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70355725

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档