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.

If you want a less performance hungry solution if you have many of such coroutines there’s another little hacky solution which should 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 UnityWebRequest, 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:
UI/WaitForUIButtons - Unify Community Wiki (archive.org)

Backup on pastebin:
WaitForUIButtons.cs - Pastebin.com

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.

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.

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.