2011-12-05 10 views
14

In qualche modo non posso credere di essere il primo a imbattersi in quel problema (e non voglio credere di essere l'unico abbastanza stupido da non vedere direttamente una soluzione), ma il mio search-fu non era forte abbastanza.Come evitare il codice spaghetti quando si utilizzano gli eventi di completamento?

Mi imbatto regolarmente in una situazione, quando devo eseguire alcuni passaggi che richiedono tempo uno dopo l'altro. Il flusso di lavoro si presenta come

var data = DataGetter.GetData(); 
var processedData = DataProcessor.Process(data); 
var userDecision = DialogService.AskUserAbout(processedData); 
// ... 

Non voglio bloccare l'interfaccia utente in ogni fase, in modo che ogni metodo non ritorna immediatamente, e genera un evento una volta terminato. Ora ilarità consegue, dal momento che la mutazione del blocco di codice di cui sopra in

DataGetter.Finished += (data) => 
    { 
     DataProcessor.Finished += (processedData) => 
     { 
      DialogService.Finished(userDecision) => 
       { 
        // .... 
       } 
       DialogService.AskUserAbout(processedData); 
      } 
     DataProcessor.Process(data); 
    }; 
DataGetter.GetData(); 

Questo si legge troppo simile Continuation-passing style per i miei gusti, e ci deve essere un modo migliore per strutturare questo codice. Ma come?

+1

mi fa pensare a compiti di continuazione: http://www.blackwasp.co.uk/ContinuationTasks.aspx –

+3

Questo sarà massicciamente più facile in C# 5 con il 'await'. Quindi sì, la gente si imbatte continuamente in questo e non è abbastanza - abbastanza da renderlo effettivamente nella lingua principale. –

risposta

7

Il modo corretto sarebbe di progettare i componenti in modo sincrono ed eseguire la catena completa in un thread in background.

+0

Grazie per l'input! Ciò è reso un po 'più difficile dall'interazione dell'utente, che non può essere facilmente eseguita su un thread non UI (almeno in WPF). Ma potremmo sicuramente ottenere meno livelli di spaghetti in questo modo. – Jens

+0

Che tipo di input dell'utente stiamo parlando? –

+0

All'utente potrebbe essere richiesto di eseguire un'attività (funzionamento di una macchina) e di premere "Avanti" al termine, oppure viene chiesto cosa fare con i dati elaborati. – Jens

2

È possibile inserire tutto in un BackgroundWorker. Il codice seguente funzionerà correttamente solo se si modificano i metodi GetData, Process e AskUserAbout per l'esecuzione in modo sincrono.

Qualcosa di simile a questo:

private BackgroundWorker m_worker; 

private void StartWorking() 
{ 
    if (m_worker != null) 
     throw new InvalidOperationException("The worker is already doing something"); 

    m_worker = new BackgroundWorker(); 
    m_worker.CanRaiseEvents = true; 
    m_worker.WorkerReportsProgress = true; 

    m_worker.ProgressChanged += worker_ProgressChanged; 
    m_worker.DoWork += worker_Work; 
    m_worker.RunWorkerCompleted += worker_Completed; 
} 

private void worker_Work(object sender, DoWorkEventArgs args) 
{ 
    m_worker.ReportProgress(0, "Getting the data..."); 
    var data = DataGetter.GetData(); 

    m_worker.ReportProgress(33, "Processing the data..."); 
    var processedData = DataProcessor.Process(data); 

    // if this interacts with the GUI, this should be run in the GUI thread. 
    // use InvokeRequired/BeginInvoke, or change so this question is asked 
    // in the Completed handler. it's safe to interact with the GUI there, 
    // and in the ProgressChanged handler. 
    m_worker.ReportProgress(67, "Waiting for user decision..."); 
    var userDecision = DialogService.AskUserAbout(processedData); 

    m_worker.ReportProgress(100, "Finished."); 
    args.Result = userDecision; 
} 

private void worker_ProgressChanged(object sender, ProgressChangedEventArgs args) 
{ 
    // this gets passed down from the m_worker.ReportProgress() call 
    int percent = args.ProgressPercentage; 
    string progressMessage = (string)args.UserState; 

    // show the progress somewhere. you can interact with the GUI safely here. 
} 

private void worker_Completed(object sender, RunWorkerCompletedEventArgs args) 
{ 
    if (args.Error != null) 
    { 
     // handle the error 
    } 
    else if (args.Cancelled) 
    { 
     // handle the cancellation 
    } 
    else 
    { 
     // the work is finished! the result is in args.Result 
    } 
} 
4

Il Task Parallel Library può essere utile per tale codice. Si noti che TaskScheduler.FromCurrentSynchronizationContext() può essere utilizzato per eseguire l'attività sul thread dell'interfaccia utente.

Task<Data>.Factory.StartNew(() => GetData()) 
      .ContinueWith(t => Process(t.Result)) 
      .ContinueWith(t => AskUserAbout(t.Result), TaskScheduler.FromCurrentSynchronizationContext()); 
Problemi correlati