我創建了一個腳本來改變它所附加的 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 中的異步方法(協程)中拋出的例外。
uj5u.com熱心網友回復:
這個答案是“不要那樣做……試試這個”的例子。
編輯:如果您不想使用Tasks 并且對協程不太確定,為什么不在每個幀更新中使用簡單的基于時間的插值函式。這是協程試圖做的事情的一個更清晰的版本。
Unity 中的協程提供了一種在一系列幀上執行冗長操作的簡單方法,但它們并非沒有問題。其中之一是關于它們IEnumerator在整個 C# 社區中明顯奇怪的使用的激烈爭論(搜索此站點)。但可惜我離題了。
協程的替代品?
為什么不直接使用async/await?自從團結2018.1,統一已完全支持用于.NET 4.5 和它的基于任務的異步模式(TAP) 。一旦你這樣做了,你會發現事情變得更容易了,你會向更廣泛的代碼示例敞開心扉,并避開那些討厭的IEnumerator爭論。
微軟:
在 Unity 中,異步編程通常是通過協程完成的。但是,從 C# 5 開始,.NET 開發中異步編程的首選方法一直是基于任務的異步模式 (TAP),在 System.Threading.Task 中使用 async 和 await 關鍵字。總之,在異步函式中,您可以等待任務完成而不會阻止應用程式的其余部分更新...更多...
示例由 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.");
}
}
例外處理
取消 Unity 中的協程操作在 Unity 協程中
處理例外的正確方法是什么?
所以這是協程的首要問題之一。
答案是根本不使用協程(假設您運行的是 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!");
}
}
Now to handle specic cases such as Task cancellation you would:
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);
}
}
See also
- Using .NET 4.x in Unity, MSFT
- .NET profile support, Unity
- Arnott, A, Recommended patterns for CancellationToken, DevBlogs, Microsoft
uj5u.com熱心網友回復:
在對方的回答是好的,所有的,但實際上并不是真正的點,你正在試圖解決的使用情況。
您沒有任何異步代碼,但需要在 Unity 主執行緒中執行代碼,因為它使用 Unity API,并且大部分只能在主執行緒上使用。
一個協程是不是異步并沒有任何待辦事項與多執行緒或任務!
一個協程基本上只是一個IEnumerator,一旦它被注冊為一個協程,StartCorotuine然后 Unity 呼叫MoveNext它(它將執行所有直到下一個yield return陳述句)在Update一個幀之后(有一些特殊的 yield 陳述句,如WaitForFixedUpdate等在Unity 主執行緒上有不同的訊息堆疊,但仍然存在)。
所以例如
// 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;
}
....
也回答你的標題。確實,您不能yield return在try - catch塊內進行操作,并且通常所說的那樣,無論如何都沒有真正的理由這樣做。
如果您真的需要,您可以將您的代碼的某些部分包裝起來,而無需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;
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/385952.html
