14

mie domande sono molte. Da quando ho visto. NET 4,5, sono rimasto molto colpito. Sfortunatamente tutti i miei progetti sono .NET 4.0 e non sto pensando di migrare. Quindi vorrei semplificare il mio codice.metodi che utilizzano Sostituzione BackgroundWorker a asincrona/tpl (NET 4.0)

Attualmente, la maggior parte del mio codice che richiede solitamente abbastanza tempo per bloccare l'immagine, faccio la seguente:

BackgroundWorker bd = new BackgroundWorker(); 
bd.DoWork += (a, r) => 
    { 
     r.Result = ProcessMethod(r.Argument); 
    }; 
bd.RunWorkerCompleted += (a, r) => 
    { 
     UpdateView(r.Result); 
    }; 

bd.RunWorkerAsync(args); 

Onestamente, io sono stanco di esso. E questo diventa un grosso problema quando c'è un'interazione utente logica complessa.

mi chiedo, come semplificare questa logica? (Ricorda che sono con. Net 4.0) Ho notato alcune cose da google, ma non ho trovato nulla di facile da implementare e adatto alle mie esigenze.

ho pensato che questa soluzione qui di seguito:

var foo = args as Foo; 
var result = AsyncHelper.CustomInvoke<Foo>(ProcessMethod, foo); 
UpdateView(result); 

public static class AsyncHelper 
{ 
    public static T CustomInvoke<T>(Func<T, T> func, T param) where T : class 
    { 
     T result = null; 
     DispatcherFrame frame = new DispatcherFrame(); 
     Task.Factory.StartNew(() => 
     { 
      result = func(param); 
      frame.Continue = false; 
     }); 

     Dispatcher.PushFrame(frame); 

     return result; 
    } 
} 

io non sono sicuro circa l'impatto è sulla manipolazione del telaio dispatcher. Ma so che funzionerebbe molto bene, ad esempio, potrei usarlo in tutti gli eventi dei controlli senza preoccuparmi di congelare lo schermo. Le mie conoscenze su tipi generici, covarianza, controvarianza sono limitate, forse questo codice può essere migliorato.

Ho pensato ad altre cose usando Task.Factory.StartNew e Dispatcher.Invoke, ma nulla che sembra interessante e semplice da usare. Qualcuno può darmi un po 'di luce?

risposta

15

Si dovrebbe solo utilizzare la Libreria Task Parallel (TPL). La chiave sta specificandoper l'attuale SynchronizationContext per eventuali continuazioni in cui si aggiorna l'interfaccia utente. Per esempio:

Task.Factory.StartNew(() => 
{ 
    return ProcessMethod(yourArgument); 
}) 
.ContinueWith(antecedent => 
{ 
    UpdateView(antecedent.Result); 
}, 
TaskScheduler.FromCurrentSynchronizationContext()); 

A parte un po 'di gestione delle eccezioni quando si accede la proprietà del antecedente Result, che è tutto troppo esso. Utilizzando FromCurrentSynchronizationContext() verrà utilizzato il SynchronizationContext ambientale proveniente da WPF (ovvero il DispatcherSynchronizationContext) per eseguire la continuazione. È lo stesso che si chiama Dispatcher.[Begin]Invoke, ma si è completamente astratti da esso.

Se si voleva ottenere anche più "pulito", se controlli ProcessMethod vorrei davvero riscrivere che per restituire un Task e lasciarlo proprio come che ottiene filate up (è ancora possibile utilizzare StartNew internamente). In questo modo si astragga il chiamante dalle decisioni di esecuzione asincrone che ProcessMethod potrebbe voler fare da solo e invece devono solo preoccuparsi di concatenare una continuazione per attendere il risultato.

UPDATE 5/22/2013

Va notato che con l'avvento di .NET 4.5 e il supporto per la lingua asincrono in C# questa tecnica prescritta è superata e si può semplicemente fare affidamento su quelle caratteristiche di eseguire un'attività specifica che utilizza await Task.Run e quindi l'esecuzione successiva verrà eseguita di nuovo sul thread di Dispatcher automaticamente. Quindi qualcosa del genere:

MyResultType processingResult = await Task.Run(() => 
{ 
    return ProcessMethod(yourArgument); 
}); 

UpdateView(processingResult); 
+0

sembra una buona opzione per semplificare il mio codice, posso già immaginare come usarlo. Sebbene, non ci sarà una soluzione che non posso continuare con la stessa esecuzione del metodo? –

+0

@ J.Lennon Non ne so abbastanza della tua attuale implementazione della coda di lavoro, ma puoi certamente far funzionare questo stesso concetto con qualunque esso sia. Se hai il controllo sull'implementazione, ti consiglio di utilizzare le API di TPL DataFlow (ActionBlock ) come meccanismo di accodamento. Questo è un post completamente separato per capire tutto questo però. :) –

1

Come incapsulare il codice che è sempre lo stesso in un componente riutilizzabile? È possibile creare un Freezable che implementa ICommand, espone una proprietà di Type DoWorkEventHandler e una proprietà Result. Su ICommand.Eseguito, creerebbe un BackgroundWorker e collegherà i delegati per DoWork e Completed, utilizzando il valore di DoWorkEventHandler come gestore di eventi e gestendo Completato in modo che imposti la propria proprietà Result al risultato restituito nell'evento.

È necessario configurare il componente in XAML, utilizzando un convertitore per associare la proprietà DoWorkEventHandler a un metodo sul ViewModel (presumo che ce ne sia uno) e associare la vista alla proprietà Result del componente, in modo che diventi aggiornato automaticamente quando Result fa una notifica di modifica.

I vantaggi di questa soluzione sono: è riutilizzabile e funziona solo con XAML, quindi non più codice di colla nel ViewModel solo per la gestione di BackgroundWorkers. Se non hai bisogno del tuo processo in background per segnalare lo stato di avanzamento, potrebbe persino non essere a conoscenza dell'esecuzione su un thread in background, quindi puoi decidere in XAML se vuoi chiamare un metodo in modo sincrono o asincrono.