The way I understand `protect` and `dispose` is they are the inter-thread equivalent to `GC_ref` and `GC_unref` respectively.
Nim's per-thread GC only looks for roots in the current thread. If some objects can't be tracked using traced `ref`'s, i.e. using typed `ptr`'s or raw `pointer`'s, then you can use `GC_ref` and `GC_unref` to manually manage these untraced objects in the same thread. The typical use-case for this is when passing objects through FFI to C code. When GC objects are passed across threads through `ptr` or `pointer`, then `protect` and `dispose` do the same thing, except they are thread-safe (GC_ref/unref can only be used in same thread). You obtain a `ForeignCell` handle from the thread that owns the object -- this ensures the object won't be inadvertently collected -- and then when you're done using it in the other thread(s), you call `dispose` on the `ForeignCell` to convey to the original GC thread that it can now collect the object if it's no longer in use.
