A set of .NET Framework managed libraries for developing graphical user interfaces.
your timer is running on the UI, so will only block durning its callback. but your worker thread is 100% cpu bound and updates a UI component continuously, so these updates will block the UI thread.
if you don't want to use invoke, a common approach is a queue. the background thread inserts entries in a thread safe queue. the UI thread uses a timer to empty and process the queued items. In your case you would need to implement a debounce as there is minimum delay in loading the queue.
note: the timer uses the event loop to dispatch. but I suspect your sending so many UI update requests, there is no loop.
if you add a sleep to the background loop, I suspect your code will work.
while (_elapsed < 4010 && _running)
{
Console.WriteLine($"in while elapsed = {_elapsed}");
Thread.Sleep(10); // allow window.loop to run before next UI update
if (prog != null)
prog.Report(_elapsed);
}