Fork-join_none odd printout behavior

I put this example together years ago to check whether vendors had implemented all the fork / join / wait / disable capabilities, and all vendors did so years ago. In the example below, the odd behavior that I am observing is that at time 200, I see the join_none_msg, followed by the idle starting message, followed by the proc G, H, I starting messages. I have verified this behavior with two different simulators. They all display at time 200, but I would have expected the proc G, H, I messages to be printed first since they are positioned earlier in the code. What am I missing? The code and the output are both shown below.

`timescale 1ns/1ns
module forktest;
  logic clk;

  initial begin
    #10 clk <= '1;
    forever #5 clk = ~clk;
  end

  initial begin
    idle(2);
    fork
      proc ("A", 3);
      proc ("B", 5);
      proc ("C", 2);
    join
    join_msg;
    idle(3);
    fork
      proc ("D", 3);
      proc ("E", 5);
      proc ("F", 2);
    join_any
    join_any_msg;
    idle(8);
    fork
      proc ("G", 3);
      proc ("H", 5);
      proc ("I", 2);
    join_none
    join_none_msg;
    idle(3);
    disable fork;
    disable_fork_msg;
    idle(9);
    fork
      proc ("J", 3);
      proc ("K", 5);
      proc ("L", 2);
    join_any
    join_any_msg;
    idle(2);
    wait fork;
    wait_fork_msg;
    idle(1);
    $finish;
  end

  task automatic idle (input int n);
    $display("\nIdle at time %0d for %0d clocks\n", $time, n);
    repeat(n) @(posedge clk);
  endtask

  task automatic proc (input string s, input int n);
    $display("Start running proc %s at time %0d for %0d clocks", s, $time, n);
    repeat(n) @(posedge clk);
    $display("--- End of proc %s at time %0d ---", s, $time);
  endtask

  function void join_msg;
    $display("\"join\" at time %0d", $time);
  endfunction

  function void join_any_msg;
    $display("\"join_any\" at time %0d", $time);
  endfunction

  function void join_none_msg;
    $display("\"join_none\" at time %0d", $time);
  endfunction

  function void disable_fork_msg;
    $display("\"disable fork\" at time %0d", $time);
  endfunction

  function void wait_fork_msg;
    $display("\"wait fork\" at time %0d", $time);
  endfunction
endmodule

// Idle at time 0 for 2 clocks
//
// Start running proc A at time 20 for 3 clocks
// Start running proc B at time 20 for 5 clocks
// Start running proc C at time 20 for 2 clocks
// — End of proc C at time 40 —
// — End of proc A at time 50 —
// — End of proc B at time 70 —
// “join” at time 70
//
// Idle at time 70 for 3 clocks
//
// Start running proc D at time 100 for 3 clocks
// Start running proc E at time 100 for 5 clocks
// Start running proc F at time 100 for 2 clocks
// — End of proc F at time 120 —
// “join_any” at time 120
//
// Idle at time 120 for 8 clocks
//
// — End of proc D at time 130 —
// — End of proc E at time 150 —
// “join_none” at time 200
//
// Idle at time 200 for 3 clocks
//
// Start running proc G at time 200 for 3 clocks
// Start running proc H at time 200 for 5 clocks
// Start running proc I at time 200 for 2 clocks
// — End of proc I at time 220 —
// — End of proc G at time 230 —
// “disable fork” at time 230
//
// Idle at time 230 for 9 clocks
//
// Start running proc J at time 320 for 3 clocks
// Start running proc K at time 320 for 5 clocks
// Start running proc L at time 320 for 2 clocks
// — End of proc L at time 340 —
// “join_any” at time 340
//
// Idle at time 340 for 2 clocks
//
// — End of proc J at time 350 —
// — End of proc K at time 370 —
// “wait fork” at time 370
//
// Idle at time 370 for 1 clocks
//
// ** Note: $finish : Time: 380 ns

The output you are seeing is ok. The order of execution may not be same as the position of code in this case.
join_none acts as no blocker for thread.
The actual thread encounters three procs G,H,I and encounters join_none. At this point, the simulator will just schedule the three threads. The join_none does not wait or spins new threads and starts the next line immediately. In the next line, it encounters the idle(3). it goes into that function and displays idle message at 200. After that it encounters the “posedge clk” statement which is the blocking call. At this point the simulator will try to switch threads and execute the other threads which are procs G,H,I.