Task.Run and UI Progress Updates

It seems you're confused due the fact that part of this cross-thread machinery is hidden from developer eyes so you just have to "take and use": https://devblogs.microsoft.com/dotnet/async-in-4-5-enabling-progress-and-cancellation-in-async-apis

We introduced the IProgress interface to enable you to create an experience for displaying progress. This interface exposes a Report(T) method, which the async task calls to report progress. You expose this interface in the signature of the async method, and the caller must provide an object that implements this interface. Together, the task and the caller create a very useful linkage (and could be running on different threads).

We also provided the Progress class, which is an implementation of IProgress. You are encouraged to use Progress in your implementation, because it handles all the bookkeeping around saving and restoring the synchronization context. Progress exposes both an event and an Action callback, which are called when the task reports progress. This pattern enables you to write code that simply reacts to progress changes as they occur. Together, IProgress and Progress provide an easy way to pass progress information from a background task to the UI thread.

Just one more thing to mention: progress notification will be invoked after the part of the job is done, not just at that moment. So, if your UI thread is idling and you have spare CPU core the delay will be almost zero. If your UI thread is busy, the notification will not be invoked until the moment the UI thread is back to idle (regardless how much spare CPU cores your computer has).


Progress<T> catches the current SynchronisationContext when it is instantiated. Whenever you call Report, it secretly delegates that to the captured context. In the example, the captured context is the UI, meaning that no exceptions occur.


The Progress<T> constructor captures the current SynchronizationContext object.

The SynchronizationContext class is a facility that abstracts the particulars of the involved threading model. That is, in Windows Forms it will use Control.Invoke, in WPF it will use Dispatcher.Invoke, etc.

When the progress.Report object is called, the Progress object itself knows that it should run its delegate using the captured SynchronizationContext.

In other terms, it works because Progress has been designed to handle that without the developer having to explicitly say it.