2015-05-15 22 views
8

Mam następujący kod, który poprawnie używa paradygmatu async/await.C# async oczekuje przy użyciu 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); 
    } 
} 

Co jest odpowiednikiem sposób napisać to, gdyby zamiast przy użyciu foreach(), chcę użyć LINQ foreach()? Ten na przykład daje błąd kompilacji.

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); 
     }); 
} 

Jedyny kod, który mam do pracy bez błędu kompilacji jest to.

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); 
     }); 
} 

Martwię się, że ta metoda nie ma podpisu asynchronicznego, tylko ciało to robi. Czy to jest odpowiednik mojego pierwszego bloku kodu powyżej?

+6

'ForEach' not a linq function – Grundy

+2

Dlaczego w ogóle chcesz to zmienić? Jeśli nie jest zepsuty, nie naprawiaj go. –

+0

Doskonały post od Erica Liperta za pomocą "foreach" vs "ForEach" http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx –

Odpowiedz

0

Początkowy przykład z foreach skutecznie czeka po każdej iteracji pętli. Ostatni przykład wywołuje List<T>.ForEach(), który ma Action<T>, co oznacza, że ​​twoja asynchroniczna lambda zostanie skompilowana jako delegat void, w przeciwieństwie do standardowego Task.

W rzeczywistości metoda ForEach() będzie wywoływała "iteracje", jedna po drugiej, bez oczekiwania na zakończenie każdej z nich. Spowoduje to również propagację do twojej metody, co oznacza, że ​​AddReferenceData() może się skończyć przed wykonaniem pracy.

Więc nie, nie jest to odpowiednik i zachowuje się zupełnie inaczej. W rzeczywistości, zakładając, że jest to kontekst EF, będzie wysadzony w powietrze, ponieważ może nie być jednocześnie wykorzystywany w wielu wątkach.

Przeczytaj także o tym, że Deepu wspomina o http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx, dlaczego lepiej jest trzymać się foreach.

10

Nie. Nie jest. Ten ForEach nie obsługuje async-await i wymaga, aby lambda była async void, która powinna być tylko używana do obsługi zdarzeń. Korzystanie z niego spowoduje jednoczesne uruchomienie wszystkich operacji async i nie będzie czekać na ich zakończenie.

Możesz użyć zwykłego foreach, ale jeśli chcesz zastosować metodę rozszerzenia, potrzebujesz specjalnej wersji async.

można tworzyć samemu, że iteracje nad elementów, wykonuje operację async i await y go:

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

Zastosowanie:

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); 
    }); 
} 

inny (i zwykle bardziej wydajne) realizacja ForEachAsync byłoby uruchomienie wszystkich operacji async i tylko wtedy await wszystkie razem, ale jest to możliwe tylko wtedy, gdy twoje akcje mogą działać jednocześnie, co zawsze tak jest (np. Entity Framework):

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

Jak zauważono w komentarzach prawdopodobnie nie chcą korzystać SaveChangesAsync w foreach na początku.Przygotowanie zmian, a następnie zapisanie ich wszystkich naraz, będzie prawdopodobnie bardziej wydajne.

+0

@ i3amon dzięki, podoba mi się twoja ostatnia sugestia. Jeśli "SinkTypeCollection" jest typu bezpiecznego dla wątków, czy to działa? – SamDevx

+0

@SamDevx faktycznie, to również działa, jeśli nie jest tak, jak jest w synchronicznej części asynchronicznej lambdy. Problem jest naprawdę w "SaveChangesAsync". – i3arnon

0

Aby napisać w oczekiwaniu na metodę, twoja metoda musi zostać oznaczona jako asynchroniczna. Kiedy piszesz metodę ForEach, którą piszesz, poczekaj w swoim wyrażeniu labda, które jest w zupełnie innej metodzie, którą wywołujesz ze swojej metody. Musisz przenieść to wyrażenie lamdba do metody i oznaczyć tę metodę jako asynchroniczną, a także jako @ i3arnon powiedział: Potrzebujesz asynchronicznie oznaczonej metody ForEach, która nie jest jeszcze dostarczona przez .Net Framework. Musisz napisać to na własną rękę.

0

Dziękuję wszystkim za opinię. Biorąc część "zapisz" "zapisz" poza pętlą, uważam, że następujące 2 metody są teraz równoważne, jeden przy użyciu foreach(), inny przy użyciu .ForEach(). Jednakże, jak wspomniał Deepu, przeczytam post Erica o tym, dlaczego może być lepszy.

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

Dlaczego nie stosować metody AddRange()?

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

await context.SaveChangesAsync().ConfigureAwait(false); 
Powiązane problemy