System.ArgumentException when using coroutines

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)?
1 Like


That is really odd. For that alone I would file a bug report with Unity.

How is this coroutine being called? I noticed that the main error in question references Emit, which would seem to imply to me that you’re using the string version of StartCoroutine. Have you tried the non-string version of it?

That is indeed correct, it is being called from FixedUpdate, on the first tick of every 30th second (which seemed to be a decent time interval to poll servers for their details.). You can ignore the time class and ConsolePrintln.

        if ((time.TimeSeconds % 30 == 0) && (time.TimeTicksSecond == 0))
        {
            if (connectionActive)
            {
                ConsolePrintln("Verifying " + GameList.Count.ToString() + " servers.");
                StartCoroutine("VerifyLiveServers");
            }
        }

If i change the code to

StartCoroutine(VerifyLiveServers());

i get what appears to be the same error.

As for filing a bug report - i would need to know that what’s happening is indeed the fault of Unity, instead of me just misusing features. However, it would be nice if the official documentation explicitly mentioned that you shouldn’t use exception handling in coroutines, if that’s what’s causing this.

In this case, having several compile warnings tacked on to the beginning of an Exception log is, in itself, definitely a bug worth reporting - that’s really bizarre. I wasn’t necessarily talking about reporting your core issue as a bug at this point; you’re correct that it’s still possible that’s your fault.

Is GameList a List or some other similar collection? I ask because I’m curious if the allocation of a nested IEnumerator from the foreach is causing issues.

Yes, it’s a list of structs containing a bunch of strings.

   List<gameItem> GameList;

    struct gameItem
    {
        public string serverName;
        public string playerCount;
        public string playerMax;
        public string gameMode;
        public string gameMap;
        public string serverIP;
        public string serverPort;
    }

Does changing to a while loop with GetEnumerator() or a for loop alleviate the issue?

I was just having this problem. Doing anything with the exception in the catch fixed it.

ie

        try
        {
            input = JsonUtility.FromJson<MyClass>("sdfsdf");
        }
        catch (System.ArgumentException e)
        {
            input = new MyClass();
        }

Broke in the way you describe.

Where as the following was fine:

        try
        {
            input = JsonUtility.FromJson<MyClass>("sdfsdf");
        }
        catch (System.ArgumentException e)
        {
            Debug.Log(e.Message);
            input = new MyClass();
        }
1 Like

With this extra bit of context,

This stands out to me

I wonder if there are two IL generation phases being run – one is optimizing away the local variable “e” since it is not used and the second is complaining that it doesn’t appear. Does removing the variable name from the catch expression change the error?

        try
        {
            input = JsonUtility.FromJson<MyClass>("sdfsdf");
        }
        catch (System.ArgumentException)
        {
            input = new MyClass();
        }

There is no error using that version.

1 Like