Linq Select'te zaman uyumsuz bekliyor


180

Varolan bir programı değiştirmek gerekir ve aşağıdaki kodu içerir:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

Ama bu benim için çok garip görünüyor, her şeyden önce seçimde asyncve awaitseçimde. Göre bu cevap Stephen Cleary I tarafından bu düşmesi gerekir.

Sonra ikincisi Select sonucu seçen . Bu, görevin hiç zaman uyumsuz olmadığı ve eşzamanlı olarak gerçekleştirildiği (hiçbir şey için çok fazla çaba) olmadığı veya görev zaman uyumsuz olarak gerçekleştirileceği ve sorgunun geri kalanının yürütüldüğü anlamına gelmiyor mu?

Stephen Cleary tarafından başka bir cevaba göre yukarıdaki kodu aşağıdaki gibi yazmalıyım :

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

ve tamamen böyle mi?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

Ben bu proje üzerinde çalışırken ben ilk kod örneğini değiştirmek istiyorum ama (görünüşte çalışan) zaman uyumsuz kod değiştirme konusunda çok istekli değilim. Belki sadece hiçbir şey için endişeleniyorum ve tüm 3 kod örnekleri aynı şeyi yapıyor?

ProcessEventsAsync şöyle görünür:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}

ProceesEventAsync'in dönüş türü nedir?
tede24

@ tede24 O var Task<InputResult>olan InputResultözel bir sınıf olma.
Alexander Derck

Bence sürümlerinizi okumak çok daha kolay. Ancak, Selectgörevinizden önceki sonuçları unutmuşsunuzdur Where.
Max

Ve InputResult bir Result özelliği var değil mi?
tede24

@ tede24 Sonuç sınıfın değil görevin özelliğidir. Ve @Max sizi bekliyor Result, görevin özelliğine erişmeden sonuçları aldığınızdan emin olmalıyım
Alexander Derck

Yanıtlar:


185
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
                   .Select(t => t.Result)
                   .Where(i => i != null)
                   .ToList();

Ama bu benim için çok garip görünüyor, her şeyden önce seçimde async ve bekliyor. Stephen Cleary'nin bu cevabına göre bunları bırakabilmeliyim.

İçin çağrı Selectgeçerli. Bu iki çizgi aslında aynıdır:

events.Select(async ev => await ProcessEventAsync(ev))
events.Select(ev => ProcessEventAsync(ev))

(Eşzamanlı bir istisnanın nasıl atılacağı konusunda küçük bir fark vardır ProcessEventAsync, ancak bu kod bağlamında hiç önemli değildir.)

Sonra sonucu seçen ikinci Seçim. Bu, görevin hiç zaman uyumsuz olmadığı ve eşzamanlı olarak gerçekleştirildiği (hiçbir şey için çok fazla çaba) olmadığı veya görev zaman uyumsuz olarak gerçekleştirileceği ve sorgunun geri kalanının yürütüldüğü anlamına gelmiyor mu?

Bu, sorgunun engellendiği anlamına gelir. Yani gerçekten asenkron değil.

Yıkmak:

var inputs = events.Select(async ev => await ProcessEventAsync(ev))

önce her olay için eşzamansız bir işlem başlatır. Sonra bu satır:

                   .Select(t => t.Result)

bu işlemlerin birer birer tamamlanmasını bekleyecektir (önce ilk olayın çalışmasını, sonra bir sonraki, sonra bir sonraki, vb.) bekler.

Bu benim umurumda değilim, çünkü herhangi bir istisnayı engeller ve sarar AggregateException.

ve tamamen böyle mi?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
                                       .Where(result => result != null).ToList();

Evet, bu iki örnek eşdeğerdir. Her ikisi de tüm eşzamansız işlemleri ( events.Select(...)) başlatır , sonra eşzamansız olarak tüm işlemlerin herhangi bir sırayla ( await Task.WhenAll(...)) tamamlanmasını bekler , ardından işin geri kalanına ( Where...) devam eder.

Bu örneklerin her ikisi de orijinal koddan farklıdır. Orijinal kod engelleniyor ve istisnalar içeri girecek AggregateException.


Bunu temizlemek için şerefe! Yani içine sarılmış istisnalar yerine AggregateExceptionikinci kodda birden fazla ayrı istisnalar olsun?
Alexander Derck

1
@AlexanderDerck: Hayır, hem eski hem de yeni kodda yalnızca ilk istisna ortaya çıkacaktı. Ama Resultonunla sarılırdı AggregateException.
Stephen Cleary

Bu kodu kullanarak ASP.NET MVC denetleyicimde bir kilitlenme alıyorum. Task.Run (…) kullanarak çözdüm. Bu konuda iyi bir his yok. Ancak, bir zaman uyumsuz xUnit testinde çalışırken tam olarak bitti. Neler oluyor?
SuperJMN

2
@SuperJMN: Değiştir stuff.Select(x => x.Result); ileawait Task.WhenAll(stuff)
Stephen Cleary

1
@DanielS: Bunlar konum esasen aynı. Durum makineleri, bağlamı yakalamak, senkron istisnaların davranışı gibi bazı farklılıklar vardır. Blog.stephencleary.com/2016/12/eliding-async-await.html adresinde daha fazla bilgi
Stephen Cleary

25

Mevcut kod çalışıyor, ancak iş parçacığını engelliyor.

.Select(async ev => await ProcessEventAsync(ev))

her etkinlik için yeni bir Görev oluşturur, ancak

.Select(t => t.Result)

her yeni görevin bitmesini bekleyen iş parçacığını engeller.

Diğer yandan kodunuz aynı sonucu verir, ancak eşzamansız tutar.

İlk kodunuz hakkında sadece bir yorum. Bu hat

var tasks = await Task.WhenAll(events...

tek bir Görev üretecek, böylece değişken tekil olarak adlandırılmalıdır.

Sonunda son kodunuz aynı ama daha özlü

Referans için: Görev Bekle / Görev.


Yani ilk kod bloğu aslında senkronize olarak yürütülür?
Alexander Derck

1
Evet, çünkü Sonuç'a erişmek iş parçacığını engelleyen bir Bekleme üretir. Öte yandan yeni bir Görev ürettiğinde bekleyebilirsiniz.
tede24

1
Bu soruya geri dönerek ve tasksdeğişkenin adı hakkındaki görüşünüze bakarak tamamen haklısınız. Korkunç seçim, hemen bekledikleri için görev bile değiller. Soruyu olduğu gibi bırakacağım
Alexander Derck

13

Linq'te mevcut yöntemler ile oldukça çirkin görünüyor:

var tasks = items.Select(
    async item => new
    {
        Item = item,
        IsValid = await IsValid(item)
    });
var tuples = await Task.WhenAll(tasks);
var validItems = tuples
    .Where(p => p.IsValid)
    .Select(p => p.Item)
    .ToList();

Umarım .NET'in aşağıdaki sürümleri, görev koleksiyonlarını ve koleksiyon görevlerini işlemek için daha zarif bir araç geliştirecektir.


12

Bu kodu kullandım:

public static async Task<IEnumerable<TResult>> SelectAsync<TSource,TResult>(this IEnumerable<TSource> source, Func<TSource, Task<TResult>> method)
{
      return await Task.WhenAll(source.Select(async s => await method(s)));
}

bunun gibi:

var result = await sourceEnumerable.SelectAsync(async s=>await someFunction(s,other params));

5
Bu sadece mevcut işlevselliği daha belirsiz bir şekilde imo ile sarar
Alexander Derck

. Alternatif Çok çalışır = bekliyoruz Task.WhenAll (sourceEnumerable.Select (zaman uyumsuz s => bekliyoruz birFonksiyon (s, diğer parametreler)) var result, ama LINQy değil
Siderit Zackwehdex

Olmamalı Func<TSource, Task<TResult>> methodihtiva other paramskodunun ikinci bit söylediniz
matramos

2
Ek parametreler harici, yürütmek istediğim işleve bağlı olarak, uzantı yöntemi bağlamında ilgisizdir.
Siderite Zackwehdex

4
Bu hoş bir uzatma yöntemi. Neden "daha karanlık" olarak kabul edildiğinden emin değilim - senkronize anlamsal olarak benzer Select(), bu yüzden zarif bir bırakma.
nullPainter

11

Bunu bir uzantı yöntemi olarak tercih ederim:

public static async Task<IEnumerable<T>> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
    return await Task.WhenAll(tasks);
}

Böylece yöntem zincirleme ile kullanılabilir:

var inputs = await events
  .Select(async ev => await ProcessEventAsync(ev))
  .WhenAll()

1
WaitAslında beklemediğinde yöntemi çağırmamalısınız . Tüm görevler tamamlandığında tamamlanmış bir görev oluşturur. Deyin WhenAllgibi Tasko emüle yöntemle. Yöntemin olması da anlamsızdır async. Sadece ara WhenAllve onunla bitir.
Servy

Sadece orijinal yöntemi çağırdığında bence işe yaramaz bir sarıcı
Alexander Derck

@Servy fair point, ancak özellikle ad seçeneklerinden hiç hoşlanmıyorum. WhenAll kulağa tam olmayan bir olay gibi geliyor.
Daryl

3
@AlexanderDerck avantajı yöntem zincirlemesinde kullanabilmenizdir.
Daryl

1
@Daryl çünkü WhenAll , değerlendirilmiş bir liste döndürdüğü için (tembel olarak değerlendirilmez), Task<T[]>bunu belirtmek için dönüş türünü kullanmak için bir argüman yapılabilir . Beklendiği zaman, bu hala Linq'i kullanabilecektir, ancak tembel olmadığını da bildirecektir.
JAD
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.