2011-12-05 13 views
14

De alguna manera no puedo creer que soy el primero en encontrarme con ese problema (y no quiero creer que soy el único lo suficientemente estúpido como para no ver una solución directamente), pero mi búsqueda no era sólida. suficiente.¿Cómo evitar el código de spaghetti cuando se usan eventos de finalización?

Me encuentro con frecuencia en una situación en la que necesito hacer algunos pasos que requieren mucho tiempo, uno después del otro. El flujo de trabajo se parece

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

no quiero bloquear la interfaz de usuario durante cada paso, por lo que cada método no respondió de inmediato, y provoca un evento, una vez que haya terminado. Ahora se produce hilaridad, ya que muta el bloque de código anterior en

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

Esto lee demasiado a Continuation-passing style para mi gusto, y tiene que haber una mejor manera de estructurar este código. ¿Pero cómo?

+1

me hace pensar en las tareas de continuación: http://www.blackwasp.co.uk/ContinuationTasks.aspx –

+3

Esto será más fácil masivamente en C# 5 con el 'await'. Así que sí, la gente se encuentra con esto todo el tiempo y no es bonita, lo suficiente como para convertirlo en el lenguaje central. –

Respuesta

7

La forma correcta sería diseñar sus componentes de forma síncrona y ejecutar la cadena completa en una cadena de fondo.

+0

¡Gracias por la entrada! Esto se hace un poco más difícil por la interacción del usuario, que no puede hacerse fácilmente en un hilo que no sea UI (al menos en WPF). Pero ciertamente podríamos obtener menos niveles de espagueti de esa manera. – Jens

+0

¿Qué tipo de entrada del usuario estamos hablando? –

+0

Se le puede pedir al usuario que realice una tarea (operar una máquina) y presione "Siguiente" cuando haya terminado, o se le preguntará qué hacer con el datado procesado. – Jens

2

Puede poner todo en un BackgroundWorker. El siguiente código sólo funcionaría adecuadamente si cambia los métodos GetData, proceso y AskUserAbout para funcionar de forma sincronizada.

Algo como esto:

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

El Task Parallel Library puede ser útil para dicho código. Tenga en cuenta que TaskScheduler.FromCurrentSynchronizationContext() se puede utilizar para ejecutar la tarea en el subproceso de la interfaz de usuario.

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