+1

Coroutine trong Unity là gì? Có liên quan tới Threads hay không?

Phuongne, Th11 17, 2019

Sơ qua một chút về Coroutine và StartCoroutine(..)

Chính xác, đúng như các bạn nghĩ, Coroutine KHÔNG phải là Threads.

Okay, đa phần chúng ta đã biết về tác dụng “thần kỳ” của Coroutine như:

  • Thực thi câu lệnh một cách tuần tự qua các frames, chờ một hoạt động khác hoàn thành trước mới tiếp tục thực thi.
  • Chạy các câu lệnh một cách “song song”.
  • Thực thi câu lệnh sau một khoảng trễ (delay).

Dưới đây là ví dụ nhỏ tạo animation fade (mờ đi) bằng Coroutine, nếu bạn tạo theo cách dưới, hiệu ứng sẽ được apply ngay lập tức trong 1 frame và bạn sẽ không thấy được animation

void Fade()
{
    for (float ft = 1f; ft >= 0; ft -= 0.1f)
    {
        Color c = renderer.material.color;
        c.a = ft;
        renderer.material.color = c;
    }
}

//src: Unity3d documents

Coroutine có thể coi là một function có khả năng pause, tạm dừng việc thực thi và trả quyền thực thi về lại cho Unity, sau đó lại có thể thực thi tiếp ở nơi mà nó tạm dừng. Ở ví dụ dưới đây, yield return null sẽ tạm dừng frame đang thực thi, và tiếp tục chạy ở frame tiếp theo.

IEnumerator Fade()
{
    for (float ft = 1f; ft >= 0; ft -= 0.1f)
    {
        Color c = renderer.material.color;
        c.a = ft;
        renderer.material.color = c;
        yield return null;
    }
}

//src: Unity3D documents

“Sao tui thấy Invoke cũng gọi được hàm sau một khoảng thời gian đấy, có gì khác nhau giữa 2 cách làm không?”

Có chứ, nếu không rõ về sự khác nhau thì có thể tham khảo ở bài viết này: So sánh nhẹ Coroutines và Invoke

“Vậy StartCoroutine() là gì?”

StartCoroutine đơn giản là cách để gọi một Coroutine thực thi, ví dụ ở trường hợp animation Fade: StartCoroutine(Fade())

Coroutine và Threads

Đầu tiên, chúng ta phải xác định rõ ràng, Coroutine không phải là Threads và không xử lý bất đồng bộ. Nói qua về Multithreading, trong một máy tính đa bộ xử lý (multiprocessor hoặc multicore processor), một thread này có thể chạy code song song với thread chính và các thread khác, thường xảy ra các vấn đề shared resources.

Với coroutine, đây không phải là một threads khác, mà nó được thực thi NGAY TRÊN thread chính của game, vì vậy không cần phải lo về vấn đề bất đồng bộ, locking… Vậy Coroutine là gì?
“Theo mình”, coroutine là một khối lệnh có thể được chia nhỏ và thực thi trên nhiều frames cho đến khi gặp exit point “}” hoặc “yield break”

Q: Vì sao Coroutine hoạt động được như vậy?

Các bạn vào IEnumerator & IEnumerable tham khảo nhé.

Main thread và Coroutine

Như mình nói ở trên thì Coroutine thực thi ở thread chính, vậy nó thực thi lúc nào?

image.png

Coroutine trong Thread chính

Nó sẽ được gọi thực thi tiếp bằng hàm MoveNext() ở 3 thời điểm, sau bước Update, sau Rendering, và sau Physics

  • Sau Update nếu sử dụng yield null, yield WaitForSeconds, yield WWW, yield StartCoroutine
  • Sau Rendering nếu sử dụng yield WaitForEndOfFrame
  • Sau các xử lý vật lý (Physics) khi sử dụng yield WaitForFixedUpdate

“Ai gọi thực thi tiếp bằng MoveNext()?”

Khi StartCoroutine, Coroutine sẽ được đưa vào stack của MonoBehaviour, tất nhiên sẽ được System gọi thực thi thông qua hàm MoveNext().

Tham khảo thêm ở ĐÂY

Sử dụng yield hợp lý

Trong 1 frame, Coroutine có thể được gọi MoveNext() từ 1-2 lần.

private void Start()
    {
        StartCoroutine(CorouTesting());
    }
    IEnumerator CorouTesting()
    {
        Debug.Log("Start Coroutine");
        while (true)
        {
            yield return new WaitForEndOfFrame();
            Debug.Log("After yield WaitForEndOfFrame");
            yield return null;
            Debug.Log("After yield null");
        }
    }
    
    int i = 0;
    private void Update()
    {
        Debug.Log("Frame: " + ++i);
    }
}

image.png Log của ví dụ trên

Nếu sử dụng yield WaitForFixedUpdate cũng vậy, trong một frame FixedUpdate có thể gọi nhiều lần.

Một số loại yield hay sử dụng:

  • yield WaitForSeconds: tiếp tục thực thi sao một khoảng thời gian, sau Update của frame đó.
  • yield WaitForFixedUpdate: tiếp tục thực thi sau tất cả FixedUpdate đã được thực thi.
  • yield WWW: thực thi sau khi WWW downloads hoàn tất.
  • yield StartCoroutine(…): thực thi sau khi coroutine trong hàm này hoàn thành trước.
  • Tự tạo một loại YieldInstruction implement từ CustomYieldInstruction
  • yield WaitForSecondsRealtime (timeScale luôn = 1), yield WaitUntil, yield WaitWhile…

Disclaimer

Bài viết từ năm 2019, chỉ có giá trị tham khảo bạn nhé 😉


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.