Architettura dell'applicazione MV # multithreaded C #

0

Ho un'applicazione incorporata in C # che usa il pattern MVVM che avrà 40-50 test diversi che devono essere eseguiti. Voglio mostrare ogni test come una voce di elenco con una barra di avanzamento di quanto è progredito ogni test. Voglio eseguire più test contemporaneamente e in futuro potrebbero esserci delle dipendenze in cui determinati test devono essere eseguiti prima dell'esecuzione di altri test. Sto cercando di capire come impostare i thread per gestire questi requisiti. Sono a conoscenza del ThreadPool , ma non lo sono sicuro che funzionerà per le mie esigenze. Quali sono i limiti di Thread Pool e c'è un modo migliore per sviluppare questi requisiti?

    
posta Tim Hutchison 27.04.2017 - 21:31
fonte

1 risposta

2

In primo luogo - presumo che il thread che esegue il test abbia qualche meccanismo in grado di notificare in qualche modo i propri progressi (magari con event o delegate ?)

Nello spirito di MVVM, considera un Model che rappresenta lo stato e il progresso del test che puoi aggiornare dal thread di test.

Lo scopo principale di Model sarebbe ricordare lo stato della barra di avanzamento dell'interfaccia utente (ad esempio, int per una percentuale). Ad esempio:

public class TestModel : INotifyPropertyChanged
{
    private int _progressPercentage;
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public int ProgressPercentage
    {
        get { return _progressPercentage; }
        set
        {
            OnPropertyChanged();
            _progressPercentage = value;
        }
    }
}

Con i framework UI basati su MVVM (come Silverlight, WPF, ecc.) è necessario un modo per garantire che tutti gli aggiornamenti che interessano lo stato dell'interfaccia utente (e quindi le proprietà su Model ) vengano richiamati utilizzando Dispatcher thread (il thread principale / dell'interfaccia utente).

(Related: link )

Potresti avere quanti thread di ThreadPool vuoi, ma quei thread dovranno richiamare le modifiche allo stato dell'interfaccia utente sul thread Dispatcher quando cambi le proprietà su Model , poiché INotifyPropertyChanged causa lo stato dell'interfaccia utente da modificare.

Inoltre, quando si ha a che fare con più componenti dello stesso tempo, una ObservableCollection<> è una scelta tipica; dove ogni modello può legarsi a un componente Barra di avanzamento dell'interfaccia utente (ad es. <ListBox> ). Di nuovo, qualsiasi tipo di modifica come Add o Remove deve avvenire sul thread Dispatcher .

Ecco un possibile esempio per un ViewModel in cui l'azione di iniziare un thread di test ha origine da un ICommand (clic del pulsante forse?)

public class MyTestViewModel
{
    public ObservableCollection<TestModel> AllRunningTests { get; set; } =
        new ObservableCollection<TestModel>();
    public ICommand RunTestCommand { get; set; }

    public MyTestViewModel()
    {
        RunTestCommand = new DelegateCommand<object>(RunTest, _ => true);
    }

    private void RunTest(object parameter)
    {
        var testModel = new TestModel();
        AllRunningTests.Add(testModel);
        Task.Run(() =>
        {
            while (testModel.ProgressPercentage < 100)
            {
                Thread.Sleep(200); // Simulate an expensive process with a sleep
                Dispatcher.Invoke(() => { testModel.ProgressPercentage += 5; });
            }
        });
    }
}

Nota che Task.Run esegue la sua azione su un thread del threadpool - Ulteriori informazioni sul blog di Stephen Cleary - Creazione compiti

Nessuno del codice sopra riportato trae vantaggio dalle capacità complete async / await o Task .

Tra i motivi per usare Task invece di avviare un thread del threadpool, includi direttamente:

  • Cancellazione dell'utente di un thread in esecuzione ( CancellationTokenSource )
  • Timeout ( Task.WhenAny e Task.Delay ))
  • Gestione semplificata delle eccezioni
  • Restituzione di valori da un thread ( Task.TrySetResult ).
  • Verifica del completamento del thread o dello stato di errore

Per queste cose potresti trovare utile il% co_de di Stephen: Programmazione asincrona: pattern per MVVM asincrono Applicazioni: comandi

Il AsyncCommand può aiutare a semplificare il compito di "concatenare" più test dipendenti. Ad esempio, un elenco di oggetti AsyncCommand .

Questo snippet presuppone che TestModel si verifichi in un RunTestsAsync , ogni AsyncCommand viene eseguito al completamento dell'attività precedente, ma gli identificatori Task / async impediscono il blocco del thread Dispatcher mentre ogni await attende il completamento:

private async Task RunTestsAsync(object parameter)
{
    var testQueue = GetSomeListOfTests(); 

    foreach (var testModel in testQueue)
    {
        AllRunningTests.Add(testModel); 

        await Task.Run(() => 
        {
            while (testModel.ProgressPercentage < 100)
            {
                Thread.Sleep(200); 
                Dispatcher.Invoke(() => testModel.ProgressPercentage += 5);
            }
        });
    }
}

Nota: i frammenti di codice sopra non vengono testati o controllati in un compilatore, ma i concetti sono più importanti del codice effettivo.

Le funzioni Task , Task e async di C # possono essere un po 'pacchiane la prima volta che le vedi. Si consiglia di eseguire alcune letture aggiuntive e di provare esempi in un'applicazione di test per familiarizzare con il modo in cui funzionano nella pratica.

La cosa più importante da realizzare è che mentre% tradizionaleawait blocca il thread dell'interfaccia utente, la parola chiave Thread.Wait non bloccherà il thread dell'interfaccia utente ed è un modo molto elegante di gestire attività a esecuzione prolungata all'interno di un comando Clic pulsante o altro gestore di eventi GUI.

    
risposta data 28.04.2017 - 05:09
fonte

Leggi altre domande sui tag