Unity中协程的异常处理
Exception handling of coroutines in Unity
我创建了一个脚本来更改它所附加的 GameObject 的透明度,我在一个需要取消的褪色协程中进行透明度更改(并且每次我们用新值调用 ChangeTransparency()
时取消) .我设法让它按照我想要的方式工作,但我想处理淹没我的控制台的 OperationCanceledException
。我知道您 不能 将 yield return
语句包装在 try-catch
块中。
在 Unity 协程中处理异常的正确方法是什么?
这是我的脚本:
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 中异步方法(协程)中抛出的这些异常。
这个答案是 “don’t do that...try this instead”.
的一个例子
编辑: 如果您不想使用 Task
s 并且对协程不太确定,为什么不使用简单的基于时间的插值函数在每个帧更新中。这是协程正在尝试做的事情的更简洁版本。
Unity 中的协程提供了一种简单的方法来对一系列帧执行冗长的操作,但是它们并非没有问题。其中之一是关于他们在整个 C# 社区中对 IEnumerator
的明显奇怪使用的激烈争论(搜索此站点)。但是,唉,我离题了。
协程的替代方案?
为什么不直接使用 async/await
?自从 Unity 2018.1 以来,Unity 就 全面支持 .NET 4.5+ 并随之 基于任务的异步模式 (TAP)。一旦你这样做了,你会发现事情变得更容易了,你打开了自己的大门,接触到更广泛的代码示例,并且你避开了讨厌的 IEnumerator
辩论。
MSDN:
In Unity, asynchronous programming is typically accomplished with coroutines. However, since C# 5, the preferred method of asynchronous programming in .NET development has been the Task-based Asynchronous Pattern (TAP) using the async and await keywords with System.Threading.Task. In summary, in an async function you can await a task's completion without blocking the rest of your application from updating... More...
示例由 MSDN 提供:
// .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.");
}
}
异常处理
Cancel coroutine operations in Unity
What is a proper way to handle Exceptions inside Unity coroutines?
所以这是协程的第一个问题。
答案是完全不使用协程(假设你是运行Unity 2018.1+)并开始迁移到async/await
。然后可以进行异常处理如:
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!");
}
}
现在要处理特定情况,例如任务取消,您将:
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);
}
}
另见
- Using .NET 4.x in Unity,微软
- .NET profile support,统一
- Arnott, A, Recommended patterns for CancellationToken, DevBlogs, Microsoft
other answer 很好,但实际上并不适合您要解决的用例。
您没有任何异步代码,但需要在 Unity 主线程中执行代码,因为它使用的是 Unity API,并且大部分代码只能在主线程中使用。
A Coroutine 是 NOT ASYNC 并且与多线程或任务无关!
协程基本上只是一个 IEnumerator
一旦它被 StartCorotuine
注册为协程然后 Unity 在其上调用 MoveNext
(它将执行所有内容直到下一个 yield return
语句)就在 Update
之后,所以一旦在 Unity 主线程上出现一个帧(有一些特殊的 yield 语句,如 WaitForFixedUpdate
等,它们在不同的消息堆栈中处理,但仍然存在)。
例如
// Tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
或
// 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
并像
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;
}
....
也要回答你的标题。的确,您不能在 try - catch
块内执行 yield return
并且正如一般所说,无论如何都没有真正的理由这样做。
如果你确实需要,你可以将不带 yield return
的代码的某些部分包装在 try - catch
块中,然后在 catch
中进行错误处理,然后你可以再次 yield break
.
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;
}
}
我创建了一个脚本来更改它所附加的 GameObject 的透明度,我在一个需要取消的褪色协程中进行透明度更改(并且每次我们用新值调用 ChangeTransparency()
时取消) .我设法让它按照我想要的方式工作,但我想处理淹没我的控制台的 OperationCanceledException
。我知道您 不能 将 yield return
语句包装在 try-catch
块中。
在 Unity 协程中处理异常的正确方法是什么?
这是我的脚本:
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 中异步方法(协程)中抛出的这些异常。
这个答案是 “don’t do that...try this instead”.
的一个例子编辑: 如果您不想使用 Task
s 并且对协程不太确定,为什么不使用简单的基于时间的插值函数在每个帧更新中。这是协程正在尝试做的事情的更简洁版本。
Unity 中的协程提供了一种简单的方法来对一系列帧执行冗长的操作,但是它们并非没有问题。其中之一是关于他们在整个 C# 社区中对 IEnumerator
的明显奇怪使用的激烈争论(搜索此站点)。但是,唉,我离题了。
协程的替代方案?
为什么不直接使用 async/await
?自从 Unity 2018.1 以来,Unity 就 全面支持 .NET 4.5+ 并随之 基于任务的异步模式 (TAP)。一旦你这样做了,你会发现事情变得更容易了,你打开了自己的大门,接触到更广泛的代码示例,并且你避开了讨厌的 IEnumerator
辩论。
MSDN:
In Unity, asynchronous programming is typically accomplished with coroutines. However, since C# 5, the preferred method of asynchronous programming in .NET development has been the Task-based Asynchronous Pattern (TAP) using the async and await keywords with System.Threading.Task. In summary, in an async function you can await a task's completion without blocking the rest of your application from updating... More...
示例由 MSDN 提供:
// .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.");
}
}
异常处理
Cancel coroutine operations in Unity
What is a proper way to handle Exceptions inside Unity coroutines?
所以这是协程的第一个问题。
答案是完全不使用协程(假设你是运行Unity 2018.1+)并开始迁移到async/await
。然后可以进行异常处理如:
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!");
}
}
现在要处理特定情况,例如任务取消,您将:
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);
}
}
另见
- Using .NET 4.x in Unity,微软
- .NET profile support,统一
- Arnott, A, Recommended patterns for CancellationToken, DevBlogs, Microsoft
other answer 很好,但实际上并不适合您要解决的用例。
您没有任何异步代码,但需要在 Unity 主线程中执行代码,因为它使用的是 Unity API,并且大部分代码只能在主线程中使用。
A Coroutine 是 NOT ASYNC 并且与多线程或任务无关!
协程基本上只是一个 IEnumerator
一旦它被 StartCorotuine
注册为协程然后 Unity 在其上调用 MoveNext
(它将执行所有内容直到下一个 yield return
语句)就在 Update
之后,所以一旦在 Unity 主线程上出现一个帧(有一些特殊的 yield 语句,如 WaitForFixedUpdate
等,它们在不同的消息堆栈中处理,但仍然存在)。
例如
// Tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
或
// 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
并像
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;
}
....
也要回答你的标题。的确,您不能在 try - catch
块内执行 yield return
并且正如一般所说,无论如何都没有真正的理由这样做。
如果你确实需要,你可以将不带 yield return
的代码的某些部分包装在 try - catch
块中,然后在 catch
中进行错误处理,然后你可以再次 yield break
.
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;
}
}