The Setvar is a motion syncronous command which is because the command is inserted into the motion queue and so it executes only when the previous commands in the buffer got exacuted.
In your case there is no motion in your macro, but because the Setvar is motion syncronous it works like a motion command, even if the motion buffer is empty the Setvar command is put into the queue and as soon that happens the exec.Ismoving() function return value becomes True and when the command got executed then the exec.Ismoving() function return value becomes False.
So, the solution for your problem is already in your macro, you have to use the while(exec.IsMoving()){} check after setting your #var(s) and before reading them out.
The other solution is to get the value from the software side variable table, like:
exec.AddStatusmessage("Counter: " + Counter + "; #2000 Var:" + exec.ivars[varnumber]);
The exec.ivars[] array acts syncronously, so it gets set immediately when you calling the exec.Setvar function, but the downside of doing this is that if the variable is set in a g-code program and is in the motion queue then the exec.ivars variable will already have the new value before it actually executed in the motion queue.
The UCCNC software syncronises the exec.Ivars[] array using the exec.Getvar() function on all the exec.ivars variables in a loop on software stop after the motion queue went empty.
So, this is the limitation of the motion being buffered.
But if you understand how it works then it is no more a problem though.
I think my description is clear enough to let you understand.