IProgress 同步

我在C#中有以下内容

public static void Main() { var result = Foo(new Progress(i => Console.WriteLine("Progress: " + i))); Console.WriteLine("Result: " + result); Console.ReadLine(); } static int Foo(IProgress progress) { for (int i = 0; i < 10; i++) progress.Report(i); return 1001; } 

Main的一些输出是:

第一次运行:

 Result: 1001 Progress: 4 Progress: 6 Progress: 7 Progress: 8 Progress: 9 Progress: 3 Progress: 0 Progress: 1 Progress: 5 Progress: 2 

第二轮:

 Progress: 4 Progress: 5 Progress: 6 Progress: 7 Progress: 8 Progress: 9 Progress: 0 Progress: 1 Progress: 2 Result: 1001 Progress: 3 

等等…

对于每次运行,输出都是不同的。 如何同步这些方法,以便按照报告的顺序显示进度0,1,… 9,然后是1001的结果。我希望输出如下:

 Progress: 0 . . . Progress: 9 Result: 1001 

Progress <>类使用SynchronizationContext.Current属性来Post()进度更新。 这样做是为了确保ProgressChanged事件在程序的UI线程上触发,因此更新UI是安全的。 必须安全地更新,例如,ProgressBar.Value属性。

控制台模式应用程序的问题是它没有同步提供程序。 不像Winforms或WPF应用程序。 Synchronization.Current属性具有默认提供程序,其Post()方法在线程池线程上运行。 没有任何联锁,哪个TP线程首先报告其更新是完全不可预测的。 没有任何好方法可以联锁。

只是不要在这里使用Progress类,没有意义。 您在控制台模式应用程序中没有UI线程安全问题,Console类已经是线程安全的。 固定:

 static int Foo() { for (int i = 0; i < 10; i++) Console.WriteLine("Progress: {0}", i); return 1001; } 

正如汉斯的回答所说, Progress的.NET实现使用SynchronizationContext.Post发送请求。 您可以像Yves的回答一样直接报告,也可以使用SynchronizationContext.Send这样请求就会阻塞,直到接收者处理完毕。

由于参考源可用,因此实现它就像复制源并将Post更改为Send并将SynchronizationContext.CurrentNoFlow更改为SynchronizationContext.Current因为CurrentNoFlow是内部属性。

 ///  /// Provides an IProgress{T} that invokes callbacks for each reported progress value. ///  /// Specifies the type of the progress report value. ///  /// Any handler provided to the constructor or event handlers registered with /// the  event are invoked through a ///  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. ///  public class SynchronousProgress : IProgress { /// The synchronization context captured upon construction. This will never be null. private readonly SynchronizationContext m_synchronizationContext; /// The handler specified to the constructor. This may be null. private readonly Action m_handler; /// A cached delegate used to post invocation to the synchronization context. private readonly SendOrPostCallback m_invokeHandlers; /// Initializes the . 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); } /// Initializes the  with the specified callback. ///  /// A handler to invoke for each reported progress value. This handler will be invoked /// in addition to any delegates registered with the  event. /// Depending on the  instance captured by /// the  at construction, it's possible that this handler instance /// could be invoked concurrently with itself. ///  /// The  is null (Nothing in Visual Basic). public SynchronousProgress(Action handler) : this() { if (handler == null) throw new ArgumentNullException("handler"); m_handler = handler; } /// Raised for each reported progress value. ///  /// Handlers registered with this event will be invoked on the ///  captured when the instance was constructed. ///  public event EventHandler ProgressChanged; /// Reports a progress change. /// The value of the updated progress. 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 handler = m_handler; EventHandler 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); } } /// Reports a progress change. /// The value of the updated progress. void IProgress.Report(T value) { OnReport(value); } /// Invokes the action and event callbacks. /// The progress value. private void InvokeHandlers(object state) { T value = (T)state; Action handler = m_handler; EventHandler changedEvent = ProgressChanged; if (handler != null) handler(value); if (changedEvent != null) changedEvent(this, value); } } /// Holds static values for . /// This avoids one static instance per type T. internal static class ProgressStatics { /// A default synchronization context that targets the ThreadPool. internal static readonly SynchronizationContext DefaultContext = new SynchronizationContext(); } 

正如其他答案之前多次指出的那样,这是由于Progress的实施方式。 您可以为您的客户端(库的用户)提供示例代码,或者为控制台项目提供IProgress实现。 这是基本的,但应该这样做。

 public class ConsoleProgress : IProgress { private Action _action; public ConsoleProgress(Action action) { if(action == null) { throw new ArgumentNullException(nameof(action)); } _action = action; } public void Report(T value) { _action(value); } } 

这是如何编写Progress的线程问题。 你需要编写自己的IProgress来获得你需要的东西。

但是,这个场景已经告诉你一些重要的事情,虽然在这个例子中,你只是简单地做了简单的Console.Writeline语句,但在实际场景中,由于需要更长或更短的时间,某些报告可能会以其他顺序报告,所以在我看来你不应该无论如何都不依赖于订单。