【问题标题】:IProgress<T> synchronizationIProgress<T> 同步
【发布时间】:2017-12-13 06:12:53
【问题描述】:

我在 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;
}

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

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    Progress 类使用 SynchronizationContext.Current 属性来 Post() 进度更新。这样做是为了确保在程序的 UI 线程上触发 ProgressChanged 事件,以便更新 UI 是安全的。安全更新 ProgressBar.Value 属性所必需的。

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

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

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

    【讨论】:

    • 我正在设计一个将在控制台应用程序中使用的库,可能是 GUI 应用程序(包括 Web 应用程序)。我想到的另一种选择是使用委托 Action,它适用于这种情况,但我不确定这是否是最佳解决方案,如果我的 lib 是从 GUI 应用程序使用的。你怎么看?
    • 你不能为一个你不知道的应用程序做出这个决定,你不知道它的线程要求是什么,你也不能假设你正在使用任何特定的线程。所以不要,引发一个事件并让客户端应用程序处理它。
    • 实际上代码并没有你写的那么糟糕,正如 Hans 所说,你不能在你的库中做出决定,所以使用接口 IProgress 绝对是个好主意。在 GUI 应用程序中,您使用 Progress 实现,在控制台应用程序中,您可以使用自己的实现。如果您愿意,您的 IProgress 实现可以只包装 Action ,这样它就可以按照您的意愿工作。我更喜欢这个活动,现在 IProgress 似乎是标准..
    • 如此处和其他答案中所说,您可以推出自己的控制台实现,以提供给您的客户/用户作为示例,或随产品一起提供。有关基本示例,请参阅:stackoverflow.com/a/32933479/1155847
    【解决方案2】:

    Hans' answer 中所述,Progress&lt;T&gt; 的 .NET 实现使用 SynchronizationContext.Post 发送其请求。你可以让它像Yves' answer一样直接报告,或者你可以使用SynchronizationContext.Send,这样请求将被阻塞,直到接收者处理它。

    因为Reference Source is available 的实现就像复制源并将Post 更改为Send 并将SynchronizationContext.CurrentNoFlow 更改为SynchronizationContext.Current 一样简单,因为CurrentNoFlow 是一个内部属性。

    /// <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();
    }
    

    【讨论】:

      【解决方案3】:

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

      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);
          }
      }
      

      【讨论】:

        【解决方案4】:

        这是Progress&lt;T&gt; 编写方式的线程问题。您需要编写自己的 IProgress&lt;T&gt; 实现来获得所需的内容。

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

        【讨论】:

          猜你喜欢
          • 2020-07-30
          • 2013-11-08
          • 1970-01-01
          • 2012-02-05
          • 1970-01-01
          • 2013-01-24
          • 1970-01-01
          • 1970-01-01
          • 2015-06-02
          相关资源
          最近更新 更多