I have the following in C#
public static void Main()
{
var result = Foo(new Progress<int>(i =>
Console.WriteLine("Progress: " + i)));
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
static int Foo(IProgress<int> progress)
{
for (int i = 0; i < 10; i++)
progress.Report(i);
return 1001;
}
Some outputs of Main are:
First run:
Result: 1001
Progress: 4
Progress: 6
Progress: 7
Progress: 8
Progress: 9
Progress: 3
Progress: 0
Progress: 1
Progress: 5
Progress: 2
Second run:
Progress: 4
Progress: 5
Progress: 6
Progress: 7
Progress: 8
Progress: 9
Progress: 0
Progress: 1
Progress: 2
Result: 1001
Progress: 3
etc...
For every run, the output is different. How can I synchronize these methods so that progress is displayed in the order they are reported 0,1,...9 followed by the result which is 1001. I want the output to be like this:
Progress: 0
.
.
.
Progress: 9
Result: 1001
The Progress<> class uses the SynchronizationContext.Current property to Post() the progress update. This was done to ensure that the ProgressChanged event fires on the UI thread of a program so it is safe to update the UI. Necessary to safely update, say, the ProgressBar.Value property.
The problem with a console mode app is that it doesn't have a synchronization provider. Not like a Winforms or WPF app. The Synchronization.Current property has the default provider, its Post() method runs on a threadpool thread. Without any interlocking at all, which TP thread gets to report its update first is entirely unpredictable. There isn't any good way to interlock either.
Just don't use the Progress class here, there is no point. You don't have a UI thread safety problem in a console mode app, the Console class is already thread-safe. Fix:
static int Foo()
{
for (int i = 0; i < 10; i++)
Console.WriteLine("Progress: {0}", i);
return 1001;
}
As said in Hans' answer, the .NET implementation of Progress<T>
uses SynchronizationContext.Post
to send its requests. You could make it directly report like in Yves' answer or you could use SynchronizationContext.Send
so the request will block till the receiver has processed it.
Because the Reference Source is available implementing it is as easy as copying the source and changing the Post
to a Send
and changing SynchronizationContext.CurrentNoFlow
to SynchronizationContext.Current
due to CurrentNoFlow
being an internal property.
/// <summary>
/// Provides an IProgress{T} that invokes callbacks for each reported progress value.
/// </summary>
/// <typeparam name="T">Specifies the type of the progress report value.</typeparam>
/// <remarks>
/// Any handler provided to the constructor or event handlers registered with
/// the <see cref="ProgressChanged"/> event are invoked through a
/// <see cref="System.Threading.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.
/// </remarks>
public class SynchronousProgress<T> : IProgress<T>
{
/// <summary>The synchronization context captured upon construction. This will never be null.</summary>
private readonly SynchronizationContext m_synchronizationContext;
/// <summary>The handler specified to the constructor. This may be null.</summary>
private readonly Action<T> m_handler;
/// <summary>A cached delegate used to post invocation to the synchronization context.</summary>
private readonly SendOrPostCallback m_invokeHandlers;
/// <summary>Initializes the <see cref="Progress{T}"/>.</summary>
public SynchronousProgress()
{
// Capture the current synchronization context. "current" is determined by Current.
// If there is no current context, we use a default instance targeting the ThreadPool.
m_synchronizationContext = SynchronizationContext.Current ?? ProgressStatics.DefaultContext;
Contract.Assert(m_synchronizationContext != null);
m_invokeHandlers = new SendOrPostCallback(InvokeHandlers);
}
/// <summary>Initializes the <see cref="Progress{T}"/> with the specified callback.</summary>
/// <param name="handler">
/// A handler to invoke for each reported progress value. This handler will be invoked
/// in addition to any delegates registered with the <see cref="ProgressChanged"/> event.
/// Depending on the <see cref="System.Threading.SynchronizationContext"/> instance captured by
/// the <see cref="Progress"/> at construction, it's possible that this handler instance
/// could be invoked concurrently with itself.
/// </param>
/// <exception cref="System.ArgumentNullException">The <paramref name="handler"/> is null (Nothing in Visual Basic).</exception>
public SynchronousProgress(Action<T> handler) : this()
{
if (handler == null) throw new ArgumentNullException("handler");
m_handler = handler;
}
/// <summary>Raised for each reported progress value.</summary>
/// <remarks>
/// Handlers registered with this event will be invoked on the
/// <see cref="System.Threading.SynchronizationContext"/> captured when the instance was constructed.
/// </remarks>
public event EventHandler<T> ProgressChanged;
/// <summary>Reports a progress change.</summary>
/// <param name="value">The value of the updated progress.</param>
protected virtual void OnReport(T value)
{
// If there's no handler, don't bother going through the [....] context.
// Inside the callback, we'll need to check again, in case
// an event handler is removed between now and then.
Action<T> handler = m_handler;
EventHandler<T> changedEvent = ProgressChanged;
if (handler != null || changedEvent != null)
{
// Post the processing to the [....] context.
// (If T is a value type, it will get boxed here.)
m_synchronizationContext.Send(m_invokeHandlers, value);
}
}
/// <summary>Reports a progress change.</summary>
/// <param name="value">The value of the updated progress.</param>
void IProgress<T>.Report(T value) { OnReport(value); }
/// <summary>Invokes the action and event callbacks.</summary>
/// <param name="state">The progress value.</param>
private void InvokeHandlers(object state)
{
T value = (T)state;
Action<T> handler = m_handler;
EventHandler<T> changedEvent = ProgressChanged;
if (handler != null) handler(value);
if (changedEvent != null) changedEvent(this, value);
}
}
/// <summary>Holds static values for <see cref="Progress{T}"/>.</summary>
/// <remarks>This avoids one static instance per type T.</remarks>
internal static class ProgressStatics
{
/// <summary>A default synchronization context that targets the ThreadPool.</summary>
internal static readonly SynchronizationContext DefaultContext = new SynchronizationContext();
}
As pointed out several times before by other answers, it's due to how Progress<T>
is implemented. You could provide your clients (users of the library) with example code, or an implementation of IProgress<T>
for a console project. This is basic, but should do.
public class ConsoleProgress<T> : IProgress<T>
{
private Action<T> _action;
public ConsoleProgress(Action<T> action) {
if(action == null) {
throw new ArgumentNullException(nameof(action));
}
_action = action;
}
public void Report(T value) {
_action(value);
}
}
This is a threading issue in how the Progress<T>
is written. You would need to write your own implementation of IProgress<T>
to get what you need.
However, this scenario already tells you something important, although in this example, you are simply doing simple Console.Writeline
statements, in real scenarios, some reports may be reported in some other order due to taking longer or shorter so in my opinion you shouldn't rely on the order anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With