How do I avoid using #0 in an interface monitor

I’ve created an interface monitor that looks like what i have below. But, on the output of my analysis port it shows that sometimes the “data” value captured at a certain clock cycle is actually the valid data from the previous cycle; in other words the posedge of clock happens but clearly the propagation of values has not reached all the signals at the same time. In the waves i see that the signals did change at the right time and my DUT has no delays on it’s output. This struck me as a race condition with my simulator, and the issue does go away if i sample at the negedge of clock or insert some kind of delay to push the reading of the signals further back in the scheduler (aka using #0;). I’ve been doing some reading on the forum about if using #0; is the correct method here and I’ve gotten mixed answers; is there a better way? Am I missing something here?

typedef struct {
 logic valid;
 logic data;
} mon_t;
interface probe_if (input clk, input rst, input logic valid, input data_t data);

class probe_t extends uvm_component;
  /* uvm boilerplate */

  virtual task run_phase(uvm_phase phase);
    mon_t mon;
    forever begin
      @(posedge clk);
      // #0; // <-- fixes the problem
      // @(negedge clk); // <-- also fixes the problem
      if (valid !== 1) continue;
      mon.valid = valid; = data;


bind /* to wherever */

Adding #0 s in your code is a temporary patch that might fix your code. The problem is when some other code adds a #0 to fix another race, your original race condition might be back.

clocking blocks are one way of addressing this problem. It samples your signals before the clock edge.

You should never use #0. This progresses only in the event scheduler and this might initiate other serious problems.
It is a good practice to sample in the monitor on the falling edge.
Using a clocking block is another solution.

Thanks for the feedback, I found that my monitor did actually have an unintended race!