In Unity3D, Coroutine is a great tool to make parallel scripts in a safe way. However, in certain cases you may need to execute a Coroutine synchronously, and Unity does not offer any method out of the box to do so.
Here is a small code snippet you can use in C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class CouroutineManager { public static void WaitCoroutine(IEnumerator func) { while (func.MoveNext ()) { if (func.Current != null) { IEnumerator num; try { num = (IEnumerator)func.Current; } catch (InvalidCastException) { if (func.Current.GetType () == typeof(WaitForSeconds)) Debug.LogWarning ("Skipped call to WaitForSeconds. Use WaitForSecondsRealtime instead."); return; // Skip WaitForSeconds, WaitForEndOfFrame and WaitForFixedUpdate } WaitCoroutine (num); } } } } |
If you prefer you can put the static method in a class you already have.
You can use it exactly like StartCoroutine, from anywhere in your code:
1 |
CouroutineManager.WaitCoroutine (myCoroutineMethod ()); |
The call will block the rest of your code until the coroutine is finished.
Please let me know in comments if you find this code useful and/or if you have other Coroutine tricks!
[EDIT 28-07-2017] Add handling for InvalidCastException.
Header image based on an image by V.ivash
By Ryan 28/07/2017 - 00:09
I was so happy to find this as it’s exactly what I need. However, I tried using this and am getting InvalidCastExceptions trying to cast the AsyncOperation func.Current into an IEnumerator. Any ideas on working around that?
By Nicolas Form 28/07/2017 - 10:37
Hello Ryan, glad it helps!
What is your version of Unity and C# framework? Do you check if func.Current is NULL before doing your cast?
If you provide your source code I may help you better.
By Ryan 28/07/2017 - 17:56
I was on Unity 5.6.1f1 and have tried updating to 2017.1 and get the same results on both using .net 3.5.
The following code produces the exception:
public class CoroutineHelper : MonoBehaviour {
private string _value = “default”;
public void GetValue()
{
CouroutineManager.WaitCoroutine(GetValueCoroutine());
Debug.Log(_value);
}
public IEnumerator GetValueCoroutine()
{
for (int i = 0; i < 5; i++)
{
yield return new WaitForSeconds(0.1f);
}
_value = "New Value";
}
}
public class CouroutineManager
{
public static void WaitCoroutine(IEnumerator func)
{
while (func.MoveNext())
if (func.Current != null)
WaitCoroutine((IEnumerator)func.Current);
}
}
and the full exception is:
InvalidCastException: Cannot cast from source type to destination type.
CouroutineManager.WaitCoroutine (IEnumerator func) (at Assets/Scripts/CoroutineHelper.cs:32)
CoroutineHelper.GetValue () (at Assets/Scripts/CoroutineHelper.cs:11)
UnityEngine.Events.InvokableCall.Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:154)
UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:637)
UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:773)
UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:52)
UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:36)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:45)
UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:50)
UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:261)
UnityEngine.EventSystems.EventSystem:Update()
By Nicolas Form 28/07/2017 - 19:13
If you try to print the type like this : “Debug.Log(func.Current.GetType().ToString())”, what type do you get? Also, for what platform do you compile? I tried only for Android and iOS platforms.
By Ryan 28/07/2017 - 19:21
Thanks for the replies Nicolas!
In this case, the func.Current type is “WaitForSeconds”. I’m compiling for Android and PC standalone.
By Nicolas Form 28/07/2017 - 19:48
I get it. I just tried your code and had the same problem. I discovered in the Unity doc that not all YieldInstructions inherits from IEnumerator, only the CustomYieldInstructions. Here are all the YieldInstructions which will raise an exception:
* WaitForSeconds
* WaitForEndOfFrame
* WaitForFixedUpdate
For WaitForEndOfFrame and WaitForFixedUpdate, it is enough to skip them (if we are synchronous we don’t care about waiting frames). But for WaitForSeconds, I guess we need to improve our code. I will investigate further and modify the article.
Nice that you spotted that bug Ryan, thanks! As a temporary solution I suggest you to use WaitForSecondsRealtime instead of WaitForSeconds.
By Ryan 28/07/2017 - 20:12
Beautiful! Thanks Nicolas!
By Nicolas Form 28/07/2017 - 20:23
I updated my post. Unfortunately it is not possible to retrieve the number of seconds passed at the creation of a WaitForSeconds, so impossible to automatically transform it to WaitForSecondsRealtime. So a best practice is to always use WaitForSecondsRealtime if there is a possibility for the coroutine to be called synchronously.
By serumas 17/10/2017 - 17:43
This code do not works…
By Nicolas Form 18/10/2017 - 07:33
Hello Serumas, it works for me and is in production right now (Unity 2017.1.1.f1).
What does not work exactly? Which version of Unity do you use?
Feel free to send me your source code if you want a better help.
By Anthony 22/02/2023 - 21:11
I know this thread is old, but I am encountering the same problem. I am running it in Unity 2022.2.0b9.
in this case I am using the code that our friend Ryan posted. Your updated example seems to catch the InvalidCastException, but the GetValueCoroutine() never gets executed.
Any thoughts on what might be the issue?
By Nicolas Form 26/02/2023 - 09:55
Hello Anthony, the code of Ryan in the comments is non-functional: it does not handle the case of non-enumerator steps (see our discussion below his code). Instead, I encourage you to use the code of the CouroutineManager in the body of my article (it has been fixed following our discussion with Ryan). If you still experience issues, send me your code and the error message you get and I’ll have a look.