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
语句,但在实际场景中,由于需要更长或更短的时间,某些报告可能会以其他顺序报告,所以在我看来你不应该无论如何都不依赖于订单。