So i’ve been writing some code with Lidgren network library (this post is not about it, nor is the problem likely caused by it) and thought that i could use Coroutines to handle reading messages from multiple clients, instead of making everything threaded, which could complicate the code (i don’t need feedback about this practice). I read the official page on Coroutines: Unity - Manual: Coroutines
Because i’ve successfully used them in the past, i started writing some code. It’s the first day after updating to 5.3 from an earlier version (5.1 or 5.0.1, can’t recall) and because i used some obsolete/deprecated methods (Application.loadLevel, because SceneManager didn’t work as expected), the API Updater popped up (may or may not be related to what i’m experiencing) - Unity - Manual: API updater
I let it finish, but now the console shows a rather long and non-descriptive (at least hard to read) error message about the piece of code that i wrote, which has coroutines. Here’s the full error message, i’ve taken the liberty of removing empty lines with Notepad++:
Which is weird, given that the error message also includes a couple of warnings in the top - something that shouldn’t normally happen (imgur screenshot of how it looks: Imgur: The magic of the Internet).
Now, i’ve tried commenting out certain lines, and this error dissappears after i comment out all of the lines containing
yield return null;
After which it simply displays the error about all code paths not returning a value (which is to be expected, because a coroutine needs the yield statment). Which is, once again, weird, because that is the exact syntax given for C# in the manual page, for making a coroutine wait until the next frame, and it shouldn’t cause problems.
Perhaps this has something to do with the type/order of the loops that i am nesting inside of the coroutine? Here’s the full source code of the coroutine in question (with the yield statements on lines 39., 51., 110.)
IEnumerator VerifyLiveServers()
{
int[] RemoveFromList = new int[MAXGAMES];
for (int i = 0; i < RemoveFromList.Length; i++)
{
RemoveFromList[i] = -1;
}
int serverIndex = 0;
int arrayIndex = 0;
NetPeerConfiguration PollConfig = new NetPeerConfiguration("SGPPoll");
NetClient poll = new NetClient(PollConfig);
foreach (gameItem server in GameList){
string ip;
int port;
try
{
ip = server.serverIP;
port = Int32.Parse(server.serverPort);
}
catch (Exception e)
{
RemoveFromList[arrayIndex] = serverIndex;
arrayIndex++;
serverIndex++;
continue;
}
poll.Start();
poll.Connect(ip, port);
uint starttime = time.TimeSeconds;
while ((poll.ConnectionStatus != NetConnectionStatus.Connected) && (poll.ConnectionStatus != NetConnectionStatus.Disconnected) && (poll.ConnectionStatus != NetConnectionStatus.None))
{
if ((time.TimeSeconds - starttime) > 4) { break;}
yield return null;
}
if (poll.ConnectionStatus == NetConnectionStatus.Connected)
{
NetOutgoingMessage pollMsg = poll.CreateMessage();
pollMsg.Write("P");
poll.SendMessage(pollMsg, poll.ServerConnection, NetDeliveryMethod.ReliableUnordered);
NetIncomingMessage pollResponse;
while ((((pollResponse = poll.ReadMessage()) == null) || (pollResponse.MessageType != NetIncomingMessageType.Data)) && (poll.ConnectionStatus == NetConnectionStatus.Connected))
{
yield return null;
}
string[] ParsedMessage;
try {
string data = pollResponse.ReadString();
ParsedMessage = data.Split(':');
}
catch (Exception e){
RemoveFromList[arrayIndex] = serverIndex;
arrayIndex++;
serverIndex++;
poll.Shutdown("0");
continue;
}
gameItem updateditem = new gameItem();
updateditem.serverName = ParsedMessage[0];
updateditem.serverIP = ParsedMessage[1];
updateditem.serverPort = ParsedMessage[2];
updateditem.playerCount = ParsedMessage[3];
updateditem.playerMax = ParsedMessage[4];
updateditem.gameMode = ParsedMessage[5];
updateditem.gameMap = ParsedMessage[6];
GameList[serverIndex] = updateditem;
poll.Recycle(pollResponse);
serverIndex++;
poll.Shutdown("0");
continue;
}
else
{
RemoveFromList[arrayIndex] = serverIndex;
arrayIndex++;
serverIndex++;
poll.Shutdown("0");
continue;
}
}
int offset = 0;
for (int i = 0; i < RemoveFromList.Length; i++)
{
if (RemoveFromList[i] != -1) {
GameList.RemoveAt(RemoveFromList[i] - offset);
offset++;
}
else
{
ConsolePrintln(i.ToString() + " dead servers removed.");
break;
}
}
yield return null;
}
As for disabling parts of code by commenting them out:
The error is not shown if:
- i disable the foreach loop in it’s entirety (lines 15-93), leaving the last yield statement (line 110.),
or
- comment out both of the while loops (lines 36-40, 49-52), leaving the last yield statement (line 110.),
or
- comment out all of the yield statements (lines 39., 51., 110.), regardless if i comment out the whole loops (lines 36-40, 49-52) or the statements only (lines 39., 51.).
The error is still shown if:
- i comment out only the yield statements inside of the loops (lines 39., 51.), BUT without removing the last one (line 110.).
This is all very confusing, and i am not even sure if it’s a Unity bug, something wrong with the way C# works, or me just doing something very, very wrong. Could anyone help me identify the problem? Furthermore, is the provided code snippet enough, or would access to the project (and the networking library .dll) be required?
Edit: after searching for the particular error message, i’ve found this post: Try / Catch in coroutines causes an internal compiler error - Unity Engine - Unity Discussions, which seems to suggest that using try / catch in subroutines breaks them. It links to an article (Wrapping Unity C# Coroutines for Exception Handling, Value Retrieval, and Locking — Tim Tregubov) suggests using a custom built class for exception handling with coroutines.
Further questions:
- wouldn’t something like this introduce the risk of breaking something down the road?
- is there another way around this supposed limitation of coroutines (handling exceptions)?
