2015-05-15 3 views
8

Ho il seguente codice che utilizza correttamente il paradigma async/await.C# async attendono utilizzando LINQ ForEach()

internal static async Task AddReferencseData(ConfigurationDbContext context) 
{ 
    foreach (var sinkName in RequiredSinkTypeList) 
    { 
     var sinkType = new SinkType() { Name = sinkName }; 
     context.SinkTypeCollection.Add(sinkType); 
     await context.SaveChangesAsync().ConfigureAwait(false); 
    } 
} 

Qual è il modo equivalente a scrivere questo caso, invece di utilizzare foreach(), che voglio usare LINQ foreach()? Questo, ad esempio, fornisce un errore di compilazione.

internal static async Task AddReferenceData(ConfigurationDbContext context) 
{ 
    RequiredSinkTypeList.ForEach(
     sinkName => 
     { 
      var sinkType = new SinkType() { Name = sinkName }; 
      context.SinkTypeCollection.Add(sinkType); 
      await context.SaveChangesAsync().ConfigureAwait(false); 
     }); 
} 

L'unico codice che ho potuto lavorare senza errore di compilazione è questo.

internal static void AddReferenceData(ConfigurationDbContext context) 
{ 
    RequiredSinkTypeList.ForEach(
     async sinkName => 
     { 
      var sinkType = new SinkType() { Name = sinkName }; 
      context.SinkTypeCollection.Add(sinkType); 
      await context.SaveChangesAsync().ConfigureAwait(false); 
     }); 
} 

Sono preoccupato che questo metodo non abbia firma asincrona, solo il corpo. È l'equivalente corretto del mio primo blocco di codice sopra?

+6

'ForEach' non una funzione linq – Grundy

+2

Perché si desidera modificarlo del tutto? Se non è rotto, non aggiustarlo. –

+0

Post di Eric Lipert eccellente utilizzando "foreach" vs "ForEach" http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx –

risposta

0

L'esempio iniziale con foreach attende in modo efficace dopo ogni iterazione del ciclo. L'ultimo esempio richiama List<T>.ForEach() che prende un significato Action<T>, che il tuo lambda async verrà compilato in un delegato void, in contrasto con lo standard Task.

In modo efficace, il metodo ForEach() invoca "iterazioni" una alla volta senza attendere che ciascuna di esse termini. Questo si propagherà anche al tuo metodo, il che significa che AddReferenceData() potrebbe finire prima che il lavoro sia finito.

Quindi no, non è un equivalente e si comporta in modo molto diverso. Infatti, supponendo che si tratti di un contesto EF, esploderà poiché potrebbe non essere utilizzato contemporaneamente su più thread.

Leggere anche http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx menzionato da Deepu per il motivo per cui è probabilmente meglio attenersi a foreach.

10

No. Non lo è. Questo ForEach non supporta async-await e richiede che il lambda sia async void che dovrebbe essere solo essere utilizzato per i gestori di eventi. Usarlo eseguirà tutte le operazioni async contemporaneamente e non attenderà il completamento.

È possibile utilizzare un normale foreach come si faceva ma se si desidera un metodo di estensione è necessaria una versione speciale .

È possibile creare uno voi stessi che scorre sopra le voci, esegue un'operazione async e await gioco è fatto:

public async Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> action) 
{ 
    foreach (var item in enumerable) 
    { 
     await action(item); 
    } 
} 

Usage:

internal static async Task AddReferencseData(ConfigurationDbContext context) 
{ 
    await RequiredSinkTypeList.ForEachAsync(async sinkName => 
    { 
     var sinkType = new SinkType() { Name = sinkName }; 
     context.SinkTypeCollection.Add(sinkType); 
     await context.SaveChangesAsync().ConfigureAwait(false); 
    }); 
} 

Un'implementazione diversa (e di solito più efficiente) di ForEachAsync vorrebbe avviare tutte le operazioni async e solo successivamente await tutte insieme ma è possibile solo se le azioni possono essere eseguite contemporaneamente quali sempre il caso (ad es. Entity Framework):

public Task ForEachAsync<T>(this IEnumerable<T> enumerable, Func<T, Task> action) 
{ 
    return Task.WhenAll(enumerable.Select(item => action(item))); 
} 

Come è stato notato nei commenti che probabilmente non si desidera utilizzare SaveChangesAsync in un foreach per cominciare.Preparare le modifiche e quindi salvarle tutte in una volta sarà probabilmente più efficiente.

+0

@ i3amon grazie mi piace il tuo ultimo suggerimento. Se "SinkTypeCollection" è di tipo thread-safe, funzionerebbe? – SamDevx

+0

@SamDevx in realtà, funzionerebbe anche se non è come nella parte sincrona del lambda async. Il problema è davvero in 'SaveChangesAsync'. – i3arnon

0

Per scrivere in attesa di un metodo, il metodo deve essere contrassegnato come asincrono. Quando scrivi il metodo di ForEach che stai scrivendo attendi all'interno dell'espressione labda che è in termini di metodo completamente diverso che stai chiamando dal tuo metodo. È necessario spostare questa espressione lamdba al metodo e contrassegnare tale metodo come asincrono e anche come @ i3arnon ha detto che è necessario il metodo ForEach marcato asincrono che non è ancora stato fornito da .Net Framework. Quindi devi scriverlo da solo.

0

Grazie a tutti per il vostro feedback. Prendendo la parte "salva" al di fuori del ciclo, credo che i seguenti 2 metodi siano ora equivalenti, uno con foreach(), un altro con .ForEach(). Tuttavia, come citato da Deepu, leggerò il post di Eric sul perché foreach potrebbe essere migliore.

public static async Task AddReferencseData(ConfigurationDbContext context) 
{ 
    RequiredSinkTypeList.ForEach(
     sinkName => 
     { 
      var sinkType = new SinkType() { Name = sinkName }; 
      context.SinkTypeCollection.Add(sinkType); 
     }); 
    await context.SaveChangesAsync().ConfigureAwait(false); 
} 

public static async Task AddReferenceData(ConfigurationDbContext context) 
{ 
    foreach (var sinkName in RequiredSinkTypeList) 
    { 
     var sinkType = new SinkType() { Name = sinkName }; 
     context.SinkTypeCollection.Add(sinkType); 
    } 
    await context.SaveChangesAsync().ConfigureAwait(false); 
} 
0

Perché non utilizzare il metodo AddRange()?

context.SinkTypeCollection.AddRange(RequiredSinkTypeList.Select(sinkName => new SinkType() { Name = sinkName }); 

await context.SaveChangesAsync().ConfigureAwait(false);