Loop with WaitForSeconds in IEnumerator appears to be incorrect by 10-15%

So I’ve had a quick look around and I’ve seen some posts about not using loops in IEnumerators, however after reading the information available on the scripting reference there’s nothing that I can see about them being particularly resource heavy or incorrect, so I figured I’d ask here.

This is the script I’m using for testing:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class iEnumeratorTest : MonoBehaviour 
{
    public bool isRecording;
    public float recordingTime;
    public float timeTaken;
    public float tickTime;

    void Update()
    {
        if (isRecording)
        {
            recordingTime += Time.deltaTime;
        }
        else
        {
            if (recordingTime != 0f)
            {
                Debug.Log("That took: " + recordingTime);
                recordingTime = 0f;
            }
        }
    }

    public void ButtonClick()
    {
        StartCoroutine(Tick());
    }

    IEnumerator Tick()
    {
        isRecording = true;
        for (float f = timeTaken; f >= 0; f -= tickTime)
        {
            yield return new WaitForSeconds(tickTime);
        }
        isRecording = false;
    }
}

Update() returns values roughly 10-15% higher than the time it should take to wait. For example, when tickTime was set to 0.1 and timeTaken set to 5, results returned varied between 5.15234 and 5.657412. The results have been similar when using both while and for loops and the actual time is closer to the desired time when increasing tickTime and decreasing timeTaken. This wouldn’t be such a big deal however as the wait times are intended to increase dramatically towards the end 10-15% is a great deal of time to be wasted.

This is due to how Coroutines and WaitForSeconds works. A coroutine is run together with Update, until it’s done. If a coroutine yields another IEnumerator, that one is called until it’s done, before the main coroutine starts getting called.

WaitForSeconds is implemented somewhat like this:

IEnumerator WaitForSeconds(float duration) {
    float startTime = Time.Time;
    while(Time.time - startTime < duration)
        yield return null;
}

This means that it will return on the first Update frame that more time than the duration has passed.

Since this is inevitably more than exactly the duration given as the argument, each WaitForSeconds will add a slight positive error. So, the lower your ticktime, the larger the error.

This isn’t actually a big problem - if you want to wait 5 seconds, use WaitForSeconds(5). The effect won’t kick in at exactly 5 seconds, but it will kick in at the first frame that’s drawn after 5 seconds has passed, so it will look exactly the same as if you somehow magically managed to make the coroutine return at exact 5 seconds.

The ‘Update’ method not depend of time. It’s called when the camera render was completed. Your coroutine wait fix seconds, so there are some difference. Try frame base coroutine:

IEnumerator Tick() {
	isRecording = true;
	float f = 0;
	while (f <= timeTaken) {
		yield return new WaitForEndOfFrame();
		f += Time.deltaTime;
	}
	isRecording = false;
}

Sorry for my english!