Preliminary. ThinkMeta ConcurrentTasks and this documentation are preliminary; everything is subject to change.

Tutorial 11: Where to go from here

On this page

What you have built

blockpack started as a sequential loop and ended as a task graph: a stream that reads ahead, a fork task that starts the encoding of every block as soon as it is loaded, encode tasks on every core, and a writer that appends in order while the disk writes behind it – with bounded memory, a clean shutdown, and output that is byte for byte the same as the sequential reference.

The method

The building blocks change from program to program. The method does not:

  1. Write or keep a sequential reference whose output is correct by definition.
  2. Measure where the time goes. Split the largest stage first.
  3. Introduce one building block at a time, because of a problem you can see – a time, a memory size, an idle thread – not because it might help.
  4. Compare the output with the reference after every step, on all cores. A test on one core misses ordering mistakes.
  5. Bound everything: blocks in flight, memory, runnable tasks. Size the scheduler for the bound, not for the input.
  6. Split serial state into a parallel part and a short serial part; a lock does not make it parallel.
  7. Give the critical path priority, and fork the parallel work at Normal explicitly.
  8. Shut down the way it runs: stop the source, drain, then destroy.
  9. Use the ready-made stages once you know what they do.

The building blocks of this tutorial

Building block Chapter What it is for
Scheduler, ConvertThreadToTask 2 Run tasks on all cores; let main wait like a task
WaitableTask 2, 3 A task that runs once and can be waited for
SchedulerInfo 4 Threads and task queue capacity
AsyncFile 5 File I/O that suspends the task, not the thread
Semaphore_s, TaskGroup_s 6 Counting free and ready slots; a reusable join
CriticalSection 7 Mutual exclusion – where the order does not matter
TaskPriority 8 Prefer the critical path
The shutdown contract 9 Stop, drain, destroy
StreamedFileReader, OrderedFileWriter 10 Ready-made read and write stages

What the tutorial did not need

These building blocks did not solve a problem of blockpack, so it did not use them. Each has its own guide:

Real-world task graphs

The same method, applied to complete encoders. Each case study shows step by step how a sequential reference became a task graph that writes byte-identical files:

  • ctLame – an MP3 encoder in which every frame depends on the state of the frame before it.
  • ctJpeg – a JPEG encoder built as a sequence of fork/join stages.
  • ctOpus – an Opus encoder that computes future frames ahead on speculation.
  • ctDeflate – a gzip compressor whose block boundaries depend on everything before them.

Their sources are in the ThinkMeta.ConcurrentTasks.Samples repository on GitHub, and the Task graphs guide collects the patterns they use.

Room for improvement

blockpack is a teaching program; a real encoder would go further:

  • Measure the stages while they run. A profiler's threading view shows where tasks wait and where threads are idle – the next thing to split.
  • Write the encoded blocks without a copy with WriteDeferred and copy tasks, keeping each slot until the writer has copied it.
  • Size the ring by measurement instead of four slots per thread.
  • Write the output with unbuffered I/O for very large files.