The reason is [the keyword `yield`][1] which has a specific meaning in C#.

On encountering the words `yield return` a function in C# returns, as one would expect.

> Using yield to define an iterator removes the need for an explicit extra class 

>[...]

>When a yield return statement is reached in the iterator method, expression is returned, and the current location in code is retained. Execution is restarted from that location the next time that the iterator function is called.

So there is no infinite loop. There is a function/iterator that can be called an infinite number of times. 

The Unity function `StartCoroutine()` makes the Unity framework call the function/iterator once per frame.  

The Unity function `StopAllCoroutines` makes the Unity framework stop calling the function/iterator.

And returning `WaitForSeconds(time)` from the iterator makes the Unity framework suspend calling the function/iterator for `time`.

----------

A confused comment and an equally confused upvote on that comment encouraged me to elaborate further on what the keyword `yield` does and doesn't do. 

If you write this: 

    IEnumerable<int> Count()
    {
       int i = 0;
       yield return i++;
    }


You can instead also write this:

    IEnumerator<int> Count() {
        return new CountEnumerator ();
    }
    class CountEnumerator : IEnumerator<int> {
        int i = 0;
        int IEnumerator<int>.MoveNext() { return i++; }
        void IEnumerator<int>.Reset() { throw new NotSupportedException(); }
    }

It follows that the keyword `yield` is not related to multi-threading and does **absolutely not** call [`System.Threading.Thread.Yield()`][2].


  [1]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield
  [2]: https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.yield?view=netframework-4.8