There are a couple of errors here:
StartCoroutine
does just that - it starts the coroutine, running it up to its next yield
statement and queuing it up to resume at the appropriate time.
It does not wait until the coroutine finishes completely before proceeding up to the next line of your Awake
method, otherwise your game would freeze if you tried to start a long-running coroutine.
If you want to delay some work like FinishRest()
until a coroutine has finished, you can either put that work at the end of the coroutine itself, or yield within an outer coroutine:
IEnumerator DoThisThenThat() {
// Start doing "this" work, possibly spread across several frames.
Coroutine thisWork = StartCoroutine(DoThisWork());
// Let the game loop keep running normally
// until "this" work has finished, before proceeding to the next line.
yield return thisWork;
// Now "this" work is done, and we can start "that" work.
DoThatWork();
}
You're missing a yield
somewhere inside your text-reading coroutine.
Remember, coroutines aren't threads. They're not running in parallel with the game's main loop, but taking turns within it.
So when you call StartCoroutine(ReadTxtCoroutine())
, it's going to do all the work up to its first yield
statement before returning control to the main game loop.
Since your only yield
statement is at the very end, that means everything that ReadTxtCoroutine()
does happens immediately, back-to-back, without a chance for the game loop to tick or display a frame in between.
If your ReadTXT()
does some work asynchronously, you'll want to yield
until that work is done before proceeding. Otherwise loadingPanel.SetActive(true)
and loadingPanel.SetActive(false)
will get called back-to-back in the same game frame, and you won't see the active state because it's been switched off again before the frame gets rendered.
If ReadTXT()
does its work synchronously (ie. a blocking call), then your game will stall until it's done, because you haven't given the main game loop a chance to take its turn in-between by yield
ing back to it. In cases like this we'll usually want to either run the blocking task on a thread instead, so it can happen in parallel, or chop it into small steps that we can yield
in between so we don't impact the framerate or responsiveness.
Q&A:
Isn't there some way to make some work wait until after the coroutine finishes?
Yes. The way I showed above, where you make another coroutine call the first coroutine, yield it until it's done, then do the work that's supposed to wait.
I tried putting a yield right before ReadTXT(), which showed the loading screen but then didn't wait of course.
It sounds ReadTXT might be finishing its work asynchronously. In that case, you'll need to instrument it in some way so that your coroutine can detect when it's done and keep yielding to the game loop until then. To show you how to do that, we'll need you to show us what it's doing by including all relevant code in your question.
It's not that easy to get coroutines to return stuff according to other posts.
Programming by hearsay will get you into trouble in Unity - there's a lot of flat-out wrong advice out there.
It's not hard to get data out of a coroutine, but because they don't execute synchronously, you need to not think of it as a return value.
Instead, you could pass in a status object or callback delegate when you start the coroutine, and the coroutine can store values into that for your code elsewhere to read them, or fire callback methods as needed.
Or you can have your coroutine fire an event or set flag variables when it gets data it wants to pass on to somewhere else.
If ReadTXT throws an exception, then FinishRest and anything after the yield
isn't called. Why is that?
Remember that the StartCoroutine
call synchronously runs the coroutine up to its first yield
statement.
So when your first yield
is after ReadTXT
, this is what your callstack looks like when the exception is thrown:
...Unity game loop stuff
ScriptA.Awake
StartCoroutine
IEnumerator.MoveNext (ie. ClassB.ReadTextCoroutine)
ClassC.ReadTXT
If none of those levels catches the exception, then the whole callstack is unwound up into the Unity game loop where it's finally output as an error message in your console log. Note that all this happens before Awake
has had a chance to move on to the line after StartCoroutine
, so it's aborted before it can FinishRest()
.
yield
) that would be even better. I'm only using a Coroutine, so I don't completely block the UI/main thread and if there was another way to display a loading screen with a single tick beforehand, I'd use it (is there? Can you use C# Tasks in Unity - like this?). – Neph Oct 04 '18 at 09:48yield
right beforeReadTxt()
, which showed the loading screen but then didn't wait of course. I even tried to "trick" it by only have the loading screen plus a tick in the Coroutine, which didn't work properly either.ReadTxt()
returns error messages, so while it might be better to make it a Coroutine directly to yield more often, it's not that easy to get Coroutines to actually return stuff like Strings according to other posts.yield
at the very end in and myReadTxt
catches an Exception/returns an "Error" String, thenFinishRest()
(yourDoThatWork()
) is never called, it doesn't even output anyDebug.Log
s I put after that lastyield
. Shouldn't it still be doing that? – Neph Oct 04 '18 at 10:40Start()
method after the firstyield
, which caused a couple of problems and I ended up moving everything from it toFinishRest()
. Luckily there are no other active scripts in the scene at that time but if I could prevent it from following the "game loop", that would be even better - in Java you can domyThread.join()
to actually wait for a thread to finish, that's what I'm looking for. – Neph Oct 04 '18 at 11:47FinishRest()
.ReadTxt
can take a couple of seconds to finish if it's a big file but it's just a normal method and there's nothing asynchronous going on. I added some code below. 3. I am using a static class every other script can access but wouldn't that mean also using awhile
to check for results?FormatException
that is thrown if the text can't be parsed into an int inProcessLine
. In thecatch
part it instantly returns one of my error messages, which breaks thewhile
inReadTxt()
with anotherreturn error;
This error is stored in my static class and is then checked inFinishRest()
to either continue or activate an error panel. Now, if I leave the lastyield
in theReadTxtCoroutine()
in, it never even reachesFinishRest
, if I don't, it shows the panel but the buttons on it don't work (they did before I added the Coroutine). – Neph Oct 04 '18 at 12:17ProcessLine()
returns the error message toReadTxt()
, which breaks the loop and returns the error to the innermost Coroutine in ClassB. With the very lastyield
there it never displays the error panel, which is what confuses me. Without it does but the buttons don't work, even though they work in normal gameplay. – Neph Oct 04 '18 at 14:14yield
at the end would prevent it from reachingFinishRest()
in ScriptA, even thoughReadTxt()
clearly finishes? Thanks again for your help! – Neph Oct 04 '18 at 14:36Log.LogError
. What I didn't know: Unity treats this as if some random exception was thrown and freezes the UI/EventSystem. I replaced it with a normalLog.Log
and the buttons are working properly again. – Neph Nov 09 '18 at 12:50