IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
            {
                // zero == every frame
                if (period == TimeSpan.Zero)
                {
                    while (true)
                    {
                        yield return(null); // not immediately, run next frame

                        if (cancellation.IsDisposed)
                        {
                            yield break;
                        }

                        MainThreadDispatcher.UnsafeSend(action);
                    }
                }
                else
                {
                    var seconds          = (float)(period.TotalMilliseconds / 1000.0);
                    var yieldInstruction = new WaitForSeconds(seconds); // cache single instruction object

                    while (true)
                    {
                        yield return(yieldInstruction);

                        if (cancellation.IsDisposed)
                        {
                            yield break;
                        }

                        MainThreadDispatcher.UnsafeSend(action);
                    }
                }
            }
            IEnumerator DelayAction(TimeSpan dueTime, Action action, ICancelable cancellation)
            {
                if (dueTime == TimeSpan.Zero)
                {
                    yield return(null);

                    if (cancellation.IsDisposed)
                    {
                        yield break;
                    }

                    MainThreadDispatcher.UnsafeSend(action);
                }
                else
                {
                    var elapsed = 0f;
                    var dt      = (float)dueTime.TotalSeconds;
                    while (true)
                    {
                        yield return(null);

                        if (cancellation.IsDisposed)
                        {
                            break;
                        }

                        elapsed += Time.deltaTime;
                        if (elapsed >= dt)
                        {
                            MainThreadDispatcher.UnsafeSend(action);
                            break;
                        }
                    }
                }
            }
            IEnumerator ImmediateAction <T>(T state, Action <T> action, ICancelable cancellation)
            {
                yield return(null);

                if (cancellation.IsDisposed)
                {
                    yield break;
                }

                MainThreadDispatcher.UnsafeSend(action, state);
            }
            // delay action is run in StartCoroutine
            // Okay to action run synchronous and guaranteed run on MainThread
            IEnumerator DelayAction(TimeSpan dueTime, Action action, ICancelable cancellation)
            {
                // zero == every frame
                if (dueTime == TimeSpan.Zero)
                {
                    yield return(null); // not immediately, run next frame
                }
                else
                {
                    yield return(new WaitForSeconds((float)dueTime.TotalSeconds));
                }

                if (cancellation.IsDisposed)
                {
                    yield break;
                }
                MainThreadDispatcher.UnsafeSend(action);
            }
            IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
            {
                // zero == every frame
                if (period == TimeSpan.Zero)
                {
                    while (true)
                    {
                        yield return(null);

                        if (cancellation.IsDisposed)
                        {
                            yield break;
                        }

                        MainThreadDispatcher.UnsafeSend(action);
                    }
                }
                else
                {
                    var startTime = Time.fixedTime;
                    var dt        = (float)period.TotalSeconds;
                    while (true)
                    {
                        yield return(null);

                        if (cancellation.IsDisposed)
                        {
                            break;
                        }

                        var ft      = Time.fixedTime;
                        var elapsed = ft - startTime;
                        if (elapsed >= dt)
                        {
                            MainThreadDispatcher.UnsafeSend(action);
                            startTime = ft;
                        }
                    }
                }
            }
            IEnumerator PeriodicAction(TimeSpan period, Action action, ICancelable cancellation)
            {
                // zero == every frame
                if (period == TimeSpan.Zero)
                {
                    while (true)
                    {
                        yield return(null); // not immediately, run next frame

                        if (cancellation.IsDisposed)
                        {
                            yield break;
                        }

                        MainThreadDispatcher.UnsafeSend(action);
                    }
                }
                else
                {
                    var elapsed = 0f;
                    var dt      = (float)period.TotalSeconds;
                    while (true)
                    {
                        yield return(null);

                        if (cancellation.IsDisposed)
                        {
                            break;
                        }

                        elapsed += Time.unscaledDeltaTime;
                        if (elapsed >= dt)
                        {
                            MainThreadDispatcher.UnsafeSend(action);
                            elapsed = 0;
                        }
                    }
                }
            }