- Home /
Best way to wait for event inside Coroutine?
Let's say I have a coroutine which waits for the player to login. The function EndLoginSequence is subscribed to an event OnEndLogin. This code works just fine, but I was wondering if there's a way to wait for the event to get called inside the coroutine. It seems like a hassle to always have a bool variable that gets set inside a function. This code is the current way I'm doing this, but I would like a 'cleaner' and shorter way to wait for the event inside the coroutine.
public event EventHandler OnEndLogin;
bool IsLogginIn = false;
IEnumerator WaitForLogin()
{
while (IsLoggingIn) yield return new WaitForEndOfFrame();
ConfirmLogin();
}
public void EndLoginSequence()
{
IsLoggingIn = false;
}
Any thoughts? Thanks in advance.
Answer by JanWosnitza · Oct 22, 2012 at 09:04 AM
I would implement another C# event "OnLogIn" on the class that initiates the log-in and inside the event start the coroutine instead of testing "IsLoggingIn" every frame.
Then I can't do stuff while the user is loggin in. The purpose of the coroutine is to animate some stuff while loggin in, inside the coroutine. I just would need something like: while(!OnEndLogin.HasBeenInvoked)
I would put the animation-logic completely outside the login-logic: Call loginAniBehaviour.startOrSo() on OnLogIn. Call loginAniBehaviour.endOrSo() on OnEndLogIn.
Of course this wouldn't remove the bool field inside loginAniBehaviour, but makes your problem more specific to starting and ending an animation (which could also be done through Animation if a simple animation fits your needs) and let's the bool field "feel" much less dirty (try to say that 3 times in a row ^^).
BUT as with all program$$anonymous$$g, why to change logic as long as it perfectly fits your needs?
Or in other words: nope I don't think there isn't a way to get rid of the bool since it holds your ani state, it has to be somewhere :)
as of while(!OnEndLogin.HasBeenInvoked): this does not exist in C# because events can be fired multiple times
Answer by andrew_pearce · Feb 29, 2020 at 07:27 AM
I had the same issue and found this topic. I ended with the following code inside coroutine:
bool isEvenFired = false;
someObject.OnSomeEven += () => {
isEvenFired = true;
}
while (!isEvenFired)
yield return new WaitForEndOfFrame();
// here your code which should be called withing coroutine only after certain even happened
I am using local variable as a switch to wait when certain even will occur to continue execution of cortoutine. I had a case with loading assets, when some other coroutine might be in the middle of loading AssetBundle so I had to wait when they will finish and then access it (since we are not allowed to load bundle more than once). I hope this code will save someone's time
Answer by Bunny83 · Feb 29, 2020 at 11:15 AM
If you want a less performance hungry solution if you have many of such coroutines there's another little hacky solution which whould work pretty well. Though you would need a lot additional information about your coroutine. Specifically you need:
keep a reference to the IEnumerator of the coroutine so we can continue it later.
keep a reference to the monobehaviour you want to run the coroutine on so we can stop and continue it. This could be just the script the coroutine is located in.
The overall idea would be something like that:
IEnumerator coroutine = null;
MonoBehaviour host = null;
bool halted = false;
void StartRoutine()
{
coroutine = MyCoroutine();
host = this;
host.StartCoroutine(coroutine);
halted = false;
}
object Suspend()
{
if (coroutine != null && host != null)
{
host.StopCoroutine(coroutine);
halted = true;
}
return null;
}
public void ContinueRoutine()
{
if (coroutine != null && host != null)
{
host.StartCoroutine(coroutine);
halted = false;
}
}
IEnumerator MyCoroutine()
{
// do something
yield return UsualStuff;
// wait for event
yield return Suspend();
// continue work when the event happend.
}
With this you can simply subscribe the ContinueRoutine()
to any event you like. Whenever the coroutine calls "Suspend" the coroutine will actually be terminated. When "ContinueRoutine" is called a new coroutine is created which will resume the coroutine where it left off. Note that the "halted" flag isn't needed in general, but it's highly recommended. Without it calling ContinueRoutine while the coroutine is still running would create two coroutines running the same statemachine which would be a total mess and nothing works as it should.
Coroutines can always be interrupted and resumed that way. I even managed to serialize a coroutine to disk and resume it the next run. However that's a very complex approach and comes with several drawbacks. First you have to manually serialize all data that the IEnumerator contains through reflection so we can actually restore the object later. The second important thing is you always loose the last executed yield instruction. So if your coroutine currently waited on a Webrequest, a WaitForSeconds, ... that will in essence have no effect since the IEnumerator will simply continue after that statement when resumed.
Some warnings:
You only want to use such an approach when the event you're waiting for takes a long time and doesn't happen too often. For example user input. Starting / Restarting a coroutine will generate garbage so this is not a solution if your "event" is happening on a regular basis. In this case it's way better to just have the coroutine busy-waiting on a boolean.
For example for UI buttons I actually created a custom yield instruction which handled the registration of the button events and carried the boolean inside. However I just can't find it anywhere ^^ google is letting me down. If I find it I will cross link it.
edit
Found it (my WaitForUIButtons.class) ^^. I actually put it on the Unity wiki. This class could even be used without a coroutine just as a multiple-buttons-to-one-event solution. It will automatically create and register a seperate callback for each button and remove them after a button has been pressed. The class is designed to be reused to avoid unnecessary garbage generation.
Your answer
Follow this Question
Related Questions
Alternative for semaphores in Unity? 2 Answers
WaitUntil Combined With sqrMagnitude is not working? 1 Answer
Wierd issue with Coroutines? 2 Answers
C# Coroutine help 1 Answer
WaitForSeconds Question 2 Answers