2013-07-31 8 views
10

Mam następujący w C#IProgress <T> synchronizacja

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

Niektóre wyjścia z głównych to:

Pierwszy bieg:

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

Drugi bieg:

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

etc ...

Dla każdego przebiegu wynik jest inny. Jak mogę zsynchronizować te metody tak, że postęp jest wyświetlany w kolejności, w jakiej zostały zgłoszone 0,1, ... 9, a następnie w wyniku których jest 1001. Chcę wyjście być tak:

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

Odpowiedz

12

Klasa Postęp> <> używa właściwości SynchronizationContext.Current do wysłania() aktualizacji postępu. Zrobiono to, aby upewnić się, że zdarzenie ProgressChanged jest uruchamiane w wątku interfejsu użytkownika programu, więc można bezpiecznie zaktualizować interfejs użytkownika. Konieczne, aby bezpiecznie zaktualizować, powiedzmy, właściwość ProgressBar.Value.

Problem z aplikacją trybu konsoli polega na tym, że nie ma ona dostawcy synchronizacji. Nie jak aplikacja WinForm czy WPF. Właściwość Synchronization.Current ma domyślnego dostawcę, jego metoda Post() działa w wątku wątku. Bez żadnego blokowania, który wątek TP najpierw zgłosi aktualizację, jest całkowicie nieprzewidywalny. Nie ma żadnego dobrego sposobu na połączenie.

Po prostu nie używaj tutaj klasy Progress, nie ma sensu. Nie masz problemu z bezpieczeństwem wątków interfejsu użytkownika w aplikacji trybu konsoli, klasa konsoli jest już wątkowo bezpieczna. Fix:

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

    return 1001; 
} 
+0

Projektuję bibliotekę, która będzie używana w aplikacji konsoli, potencjalnie aplikacji GUI (w tym aplikacjach internetowych). Inną alternatywą, o której myślałem, jest użycie delegata Action , który działa w tym scenariuszu, ale nie jestem pewien, czy jest to najlepsze rozwiązanie, jeśli moja lib jest używana z aplikacji GUI. Co myślisz? – danze

+0

Nie możesz podjąć tej decyzji w przypadku aplikacji, której nie znasz, nie masz pojęcia, jakie są jej wymagania dotyczące wątków, ani nie możesz założyć, że używasz konkretnego wątku. Więc nie rób tego, podnieś wydarzenie i pozwól aplikacji klienta sobie z tym poradzić. –

+1

Właściwie kod nie jest taki zły, jak napisałeś, jak mówi Hans, nie możesz podjąć decyzji w swojej bibliotece, więc używanie interfejsu IProgress jest zdecydowanie dobrym pomysłem. Z aplikacji GUI korzystasz z implementacji Progress, z aplikacji Console możesz korzystać z własnej implementacji. Jeśli chcesz, twoja implementacja IProgress może po prostu ominąć działanie , więc będzie działać tak, jak chcesz. Wolałbym to robić w przypadku zdarzeń, IProgress wydaje się być standardem teraz. –

1

ten to wątek, w jaki napisany jest Progress<T>. Będziesz musiał napisać własną implementację IProgress<T>, aby uzyskać to, czego potrzebujesz.

Jednak ten scenariusz mówi już o czymś ważnym, chociaż w tym przykładzie po prostu robisz proste instrukcje Console.Writeline, w prawdziwych scenariuszach niektóre raporty mogą być zgłaszane w innej kolejności z powodu zabrania dłuższego lub krótszego, więc według mnie i tak nie powinieneś polegać na zamówieniu.

2

Jak podkreślił kilkakrotnie wcześniej przez innych odpowiedzi, to ze względu na to, jak Progress<T> jest realizowany. Możesz dostarczyć swoim klientom (użytkownikom biblioteki) przykładowy kod lub implementację IProgress<T> dla projektu konsoli. Jest to podstawowe, ale powinno zrobić.

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

Jak powiedział w Hans' answer, implementacja .NET z Progress<T> wykorzystuje SynchronizationContext.Post wysłać swoje żądania. Możesz zrobić to bezpośrednio w raporcie jak w Yves' answer lub możesz użyć SynchronizationContext.Send, więc żądanie zostanie zablokowane, dopóki odbiornik go nie przetworzy.

Ponieważ Reference Source is available jej wykonania jest tak łatwe, jak kopiowanie źródło i zmieniając Post do Send i zmieniając SynchronizationContext.CurrentNoFlow do SynchronizationContext.Current powodu CurrentNoFlow będący własnością wewnętrzny.

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

To świetne rozwiązanie alternatywne - tego nie wiedziałem! Miły. –