Possible multiple enumeration - jak uniknąć

Possible multiple enumeration - jak uniknąć
Mikan
  • Rejestracja:ponad 9 lat
  • Ostatnio:12 miesięcy
  • Postów:75
0

Powiedzmy że mam coś takiego:

Kopiuj
class Foo
{
    public int Id { get; set;}
    public string Boo { get; set;}
}

public IEnumerable<Foo> Do(IEnumerable <Foo> foos)
{
    var ids = foos.Select(x => x.Id);
    var cache = api.Get(ids);
    foreach(var foo in foos)
    {
        var someData = cache.TryGetValue(foo.Id);
        if(someData != null)
        {
            foo.Boo = someData;
        }
        else 
        { //something else
        }
     yield return foo;
    }
}

foos jest bardzo ciężkie i zje całą pamięć jak dam .ToList()
R# informuje mnie że: possible multiple enumeration
Api przyjmuje tylko listę id.

Czy można to zrobić jakoś inaczej?

Grzegorz Świdwa
Grzegorz Świdwa
  • Rejestracja:ponad 5 lat
  • Ostatnio:ponad 4 lata
  • Postów:385
0
Kopiuj
public IEnumerable<obj> Do(IEnumerable<int> IDs, int PageSize)

Można wtedy wewnątrz wczytywać dane "partiami". Nie wiem czy idę w dobrym kierunku bo nie za bardzo wiem o co Ci chodzi. gdybyś chciał pobrać kilka tysięcy rekordów to może to będzie dobre?

Kopiuj
public class obj
        {
            public obj(int Id, string Value)
            {
                this.Id = Id;
                this.Value = Value;
            }

            public int Id { get; set; }
            public string Value { get; set; }
        }
        public class api
        {
            public static IEnumerable<obj> Get(IEnumerable<int> IDs)
            {
                Random random = new Random();
                foreach (int id in IDs)
                    yield return new obj(id, random.Next(100, 10000).ToString());
            }
        }

        public IEnumerable<obj> Do(IEnumerable<int> IDs, int PageSize)
        {
            int n = 0;
            do
            {
                bool LastLoop = (IDs.Count() <= n + PageSize) ? true : false;
                IEnumerable<int> IDsInCurrentLoop = (LastLoop) ? IDs.ToList().GetRange(n, PageSize) : IDs.ToList().GetRange(n, IDs.Count() - n);
                IEnumerable<obj> importedData = api.Get(IDsInCurrentLoop);
                foreach (obj o in importedData)
                    yield return o;
                n += PageSize;
            } while (n >= IDs.Count());
        }

A najlepiej to w ogóle jak obawiasz się o pamięć to podzielić sobie te ID na partie do pobrania z API i wywoływać Do dla partii danych

ŁF
Moderator
  • Rejestracja:ponad 22 lata
  • Ostatnio:dzień
0

A tak? :>

Kopiuj
     foreach(var foo in foos)
     {
         var cache = api.Get(new List<int> { foo.Id });
         // ...
     }

jasmina tmp
  • Rejestracja:prawie 8 lat
  • Ostatnio:ponad rok
  • Postów:10
0
ŁF napisał(a):

A tak? :>

Kopiuj
     foreach(var foo in foos)
     {
         var cache = api.Get(new List<int> { foo.Id });
         // ...
     }

No tak to będzie wolał Api w pętli.. chyba nie bardzo

edytowany 1x, ostatnio: ŁF
ŁF
Zdziwiłabyś się (vide post autora wątku z 12:54). Czasem szybciej jest zrobić mnóstwo drobnych rzeczy, niż naraz jedną kobyłę. Sam się kilka lat temu na tym boleśnie sparzyłem, kiedy połączyłem wiele drobnych, a kosztownych zapytań z EF w jedno duże. To jedno niby działało dużo szybciej, koszt zapytania też spadł, nawet testowałem je na prodykcyjnych danych - a mimo to produkcja klęknęła. Okazało się, że przegapiłem, że to jedno duże potrzebuje 120MB pamięci do wykonania.
ŁF
Hipotetycznie: danych jest kilkaset MB. Przesyłasz to do api, które jest webserwisem. Ile pamięci (być może ciągły blok!) będzie potrzebował serwer, zanim dane przekonwertuje do postaci wymaganej przez odpowiednią metodę api? A przecież jeszcze potrzeba pamięci na wygenerowanie odpowiedzi... Ile takich zapytań obsłuży serwer równolegle? Ile pamięci będzie potrzebował klient i na jak długo, zanim zakończy wysyłanie? A tu ponownie - trzeba odebrać odpowiedź i gdzieś ją trzymać.
ŁF
Do tego wysyłanie danych pojedyńczo lub w małych paczkach pozwala kontrolować zatrzymywanie/wznawianie pracy bez utraty całego wyniku, w przypadku błędu nie tracisz wszystkich wyników tylko jeden, a aplikacje nie zamrażają się na długi czas pracy natywnego kodu związany z przetwarzaniem dużego bloku danych.
jasmina tmp
  • Rejestracja:prawie 8 lat
  • Ostatnio:ponad rok
  • Postów:10
1

Tutaj nie chodzi o to co masz wewnątrz Do(..) tylko gdzie i jak używasz wyników z Do. tzn kiedy te wyniki materializujesz - wtedy idzie faktyczna pętla .
Jak nie zrobisz jawnie ToList to materializacja zrobi się sama przy pierwszym odwołaniu do wyniku.
Komunikat ostrzega o tym że się może zrobić (materializacja - pętla) sama kilka razy np. costam = Do.. i costam2 = Do..
Jeśli masz pewność, że nie - bo tak nie napisałeś - to jest OK.
Jeśli nie, to za pierwszym razem się zmaterializuje i za kolejnym ponownie.

Inna kwestia, że obawiasz się wszystkich wyników z Do(.., ale jeśli to miejsce gdzie wołasz Do(.. nie sprawdza tych częściowych wyników i nie przerywa pętli to i tak dostaniesz wszystkie wyniki - wtedy nie ma sensu yield.
Jeśli natomiast sprawdzasz lub wybierasz wyniki np. Do(..).Where(costam) to jest szansa że pętla będzie krótsza wtedy bym dał ToList() i operował już na tym wyniku.

edytowany 5x, ostatnio: jasmina tmp
ŁF
Nieprawda. Jeśli iterujesz po IEnumerable albo IEnumerable<T>, to materializuje się tylko kolejny element, a poprzedni znika, tzn. zostaje kiedyś "zniknięty" przez GC. Oczywiście da się to spieprzyć robiąc źle enumerator, ale trzeba by się postarać. Pośrednio stąd właśnie ostrzeżenie "possible multiple enumeration", bo jeśli pierwsza iteracja materializowałaby całą listę naraz, a nie tylko poszczególne elementy, to druga iteracja nie byłaby problemem, bo elementy i tak były by już zmaterializowane.
Mikan
  • Rejestracja:ponad 9 lat
  • Ostatnio:12 miesięcy
  • Postów:75
0

Dużo później w kodzie jest to materializowane w paczkach.

Rozwiązałem problem pobierając wszystkie dane z api (nie jest ich tak dużo).

Zadałem to pytanie dlatego że próbowałem wcześniej rozwiązania @ŁF i działa ono szybciej niż to moje, dlatego pomyślałem że to przez dwie enumeracje, ale może coś źle sprawdzałem :)

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.