From the Progress<T> Class
reference page:
Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.
I'm creating a Progress<int>
on a background thread. I want the callbacks, and any associated cancellations (throwing an OperationCancelledException
), to occur on the same thread.
At the time of constructing the Progress<int>
object, SynchronizationContext.Current
is null
.
And so, as the above documentation tells me, the callbacks are being executed on the Thread Pool...
Questions
- Is it possible to do anything about the fact that the current
SynchronizationContext
isnull
? For example, create one for the current thread? - If that is possible, would the
Progress<T>
capture thatSynchronizationContext
? - If that is possible, would that reliably mean that the callbacks are executed on the same thread? As, I've read in other places, such as this SO answer, that:
...a SynchronizationContext does not necessarily represent a specific thread; it can also forward invocation of the delegates passed to it to any of several threads (e.g. to a ThreadPool worker thread)...
Solution/workaround
To make sure that callbacks are happening on the same thread, I'm using my own implementation of the IProgress<T>
interface:
public class SynchronousProgress<T> : IProgress<T>
{
private readonly Action<T> action;
public SynchronousProgress(Action<T> action)
{
this.action = action;
}
public void Report(T value)
{
action(value);
}
}
It works. However, I'm still wondering if there is a way to achieve this with the .NET Progress<T>
class?
Update: background information
The attempted usage of the Progress<T>
class sits inside a custom, cancellable progress dialog, which encapsulates doing some work and reporting progress. In this case, the work (that can be cancelled) is occuring on the other side of a plugin boundary. It's desirable to use a .NET type (e.g. IProgress<T>
) in the plugin interface for communicating progress, rather than a custom type (e.g. our own, (older) IProgress
type).
The callback being given to the .NET IProgress<T>
implementation, is simply an instruction to increment the progress of the custom IProgress
implementation. Along the lines of:
public void Export(CulturedStreamWriter writer, IProgress progress) // that's a custom IProgress
{
progress.Steps = toExport.Count;
exporter.Export(toExport, writer, new SynchronousProgress<int>(progress.StepTo)); // increment the progress of the custom IProgress
}
Using the .NET Progress<T>
in place of the SynchronousProgress<T>
does not work, as cancellation exceptions are being thrown on a different thread to this code, which is where they need to be caught.
Seems as the custom implementation of the .NET IProgress<T>
is working (SynchronousProgress<T>
), perhaps it is in fact the most appropriate approach (given the surrounding code/constraints).