(the post is automatically translated by AI)

Introduction

While working with UniVRM [1], I noticed that many Unity development scenarios rely on Coroutines — for example, opening a system file dialog or loading resources. In these situations, Coroutines are chosen to prevent background tasks from disrupting the user experience, such as when loading a large asset causes the window to freeze and the application becomes unresponsive.

In this article, I’ll explain what a Coroutine is and compare it with Threads. I’ll then go into more detail about how Coroutines work and wrap up with a brief conclusion. If you’re interested in implementation, see the follow-up article: Why Coroutine? (Part 2) — Implementing a Coroutine in C.

Body

A Coroutine (共常式 in Mandarin) is literally a “cooperative subroutine.” Think of it as multiple subroutines sharing a single resource, taking turns using it. Among these subroutines, one acts as the producer — a special member whose job is to manage and schedule the other consumer subroutines. When a consumer finishes its turn, it yields control back to the producer, which then passes the resource to the next consumer. Consumers never hand the resource directly to each other.

Coroutine flow diagram

An analogy: imagine you and your friends order a bowl of shaved ice on a hot summer day, but the shop only gives you one spoon. To be fair, you agree to take turns — one scoop each. The bowl (resource) is shared among everyone’s task of eating ice. However, if one friend hogs the spoon and refuses to pass it, the whole system breaks down. This is exactly the pitfall of Cooperative Multitasking.

This is the core problem with Cooperative Multitasking [2], which is typically used when a single CPU core handles multiple tasks. If any consumer refuses to yield, the producer can never dispatch the next task.

Another common alternative is Multi-threading. With multi-threading, multiple tasks can run “simultaneously” note 1 on a single core, and each thread has its own context — similar to Coroutines. The key difference is that in multi-threading, the OS decides when to switch between threads (context switch), which means shared data must be protected. We need mechanisms like mutexes and signals to guard critical sections.

Comparison:

CoroutineThread
SwitchingUser decides where to yieldOS decides context switch time
Data protectionNot requiredRequired
Independent contextYesYes
OtherRuns inside a thread

Note 1: “Simultaneously” is in quotes because the CPU rapidly switches between tasks, creating an illusion of parallelism.

How It Works

To implement a Coroutine, we need four core operations: create, assign, retrieve, and switch between subroutines. First, we create a producer, which can register consumers and schedule them. Then, for each consumer, we create its own context. Each time a consumer yields, its state is saved into its context, and control returns to the producer for the next dispatch. This continues until all consumers complete their tasks.

Summary of the required components and methods:

NameTypeDescription
producerobjectCan register and schedule consumers
consumerobjectHas its own task; may be interrupted (yield)
yield()methodSuspends the current consumer and returns control to the producer
create_producer()methodCreates a producer
create_consumer()methodCreates a consumer
register_consumer()methodRegisters a new consumer with the producer
schedule()methodProducer’s scheduling logic

The overall flow:

  1. create_producer()
  2. create_consumer()
  3. register_consumer()
  4. Start running the producer
  5. Producer dispatches resource to a consumer
  6. Consumer runs
  7. Consumer calls yield()
  8. Consumer returns resource to the producer
  9. If there are unfinished consumers, go back to step 5. Otherwise, continue.
  10. Producer finishes or stands by

Conclusion

In this article, we explained how Coroutines work and made a brief comparison with Threads. We then took a closer look at the methods and flow required to implement a Coroutine. In the next article, Why Coroutine? (Part 2) — Implementing a Coroutine in C, we’ll implement the Coroutine described here using C.

References

  1. UniVRM - Github page - https://github.com/vrm-c/UniVRM
  2. Cooperative Multitasking - https://en.wikipedia.org/wiki/Cooperative_multitask
  3. System V-like - Wikipedia - https://zh.wikipedia.org/zh-tw/UNIX_System_V
  4. C/C++ coroutine (fiber) implementation - http://zevoid.blogspot.com/2017/11/cc-coroutine-fiber.html