2011-12-07 21 views
21

Böyle paralel döngü kullanarak bir şey işlemek istiyorum:Paralel.Foreach + verim dönüşü mü?

public void FillLogs(IEnumerable<IComputer> computers) 
{ 
    Parallel.ForEach(computers, cpt=> 
    { 
     cpt.Logs = cpt.GetRawLogs().ToList(); 
    }); 

} 

Tamam, gayet iyi çalışıyor. Ancak FillLogs yönteminin IEnumerable döndürmesini istersem nasıl yapmalıyım?

public IEnumerable<IComputer> FillLogs(IEnumerable<IComputer> computers) 
{ 
    Parallel.ForEach(computers, cpt=> 
    { 
     cpt.Logs = cpt.GetRawLogs().ToList(); 
     yield return cpt // KO, don't work 
    }); 

} 

DÜZENLEME

mümkün olmamaya görünüyor ... ama böyle bir şey kullanın:

public IEnumerable<IComputer> FillLogs(IEnumerable<IComputer> computers) 
{ 
    return computers.AsParallel().Select(cpt => cpt); 
} 

Ama cpt.Logs = cpt.GetRawLogs().ToList(); talimat

+0

"IEnumerable " dönüş tipiniz yükü almayacaktır. –

cevap

12

Kısa versiyon - Hayır, bu bir yineleme bloğu ile mümkün değildir; uzun versiyon muhtemelen arayanın yineleyici dizisi (dequeue yapıyor) ve paralel işçiler (enqueue) arasında senkronize kuyruk/dequeue içerir; ama bir yan not olarak - günlükler genellikle IO-bağlıdır ve IO-bağlı olan paralellikler çoğu zaman pek işe yaramaz. Arayan her tüketmek için biraz zaman alacak ise

sonra orada sadece bir seferde bir günlük işler bir yaklaşımın bazı liyakat olabilir, ama ederken arayanın önceki tüketen olduğunu yapabilir log; yani numaralı maddeyi yield'dan önce başlatır ve yield'dan sonra tamamlanmasını bekler ... ama yine de oldukça karmaşıktır. basitleştirilmiş bir örnek olarak:

static void Main() 
{ 
    foreach(string s in Get()) 
    { 
     Console.WriteLine(s); 
    } 
} 

static IEnumerable<string> Get() { 
    var source = new[] {1, 2, 3, 4, 5}; 
    Task<string> outstandingItem = null; 
    Func<object, string> transform = x => ProcessItem((int) x); 
    foreach(var item in source) 
    { 
     var tmp = outstandingItem; 

     // note: passed in as "state", not captured, so not a foreach/capture bug 
     outstandingItem = new Task<string>(transform, item); 
     outstandingItem.Start(); 

     if (tmp != null) yield return tmp.Result; 
    } 
    if (outstandingItem != null) yield return outstandingItem.Result; 
} 
static string ProcessItem(int i) 
{ 
    return i.ToString(); 
} 
+0

Tam olarak değil ama benzer bir sorunu (aşırı gözden kaçan :)) geri dönüş sonucu parallel.foreach ile yaşadım. Farklı bağlamda bunun birilerine yardımcı olabileceğini düşündüm. http://stackoverflow.com/questions/32183463/why-i-get-duplicated-values-during-a-parallel-task-execution-using-an-iterator – Spock

4

ı don nereye koyduğunuzu saldırgan olmak istemiyorum, ama belki de anlama eksikliği var. Parallel.ForEach, TPL'nin foreach'ı çeşitli donanımlarda mevcut donanıma göre çalıştıracağı anlamına gelir. Ama bu demek oluyor ki, ii bu işi paralel yapmak mümkün! yield return, bazı değerleri bir listeden (veya hiç olmayan bir şekilde) alma ve gerektiğinde tek tek geri vermenizi sağlar. Bu, koşula uyan tüm öğeleri ilk önce bulma ihtiyacını önler ve daha sonra bunların üzerine yineleme yapar. Bu gerçekten bir performans avantajı, ancak paralel olarak yapılamaz.

+1

Ancak, getiri dönüş değerlerinin her bir nesli biraz zaman alırsa, daha hızlı alabilmeniz için bir sonraki getiri dönüş değerini paralel olarak işlemek istediğiniz durumlar olmazdı. Sanırım bir tampon ya da benzeri bir şey mi? Anlama eksikliğinin olup olmadığını bilmiyorum, ancak daha hızlı bir şekilde elde etmek isteyebileceği örnekleri bile anlayabiliyorum. Verim amacının, ihtiyaç duyulduğu anda işlenmesi gerektiğine inanıyorum, bu yüzden getiri dönüşü kesinlikle uymuyor. Ama ne istediğimi kesinlikle hayal edebiliyorum ... –

0

Nasıl yaklaşık

  Queue<string> qu = new Queue<string>(); 
      bool finished = false; 
      Task.Factory.StartNew(() => 
      { 
       Parallel.ForEach(get_list(), (item) => 
       { 
        string itemToReturn = heavyWorkOnItem(item);   
        lock (qu) 
         qu.Enqueue(itemToReturn);       
       }); 
       finished = true; 
      }); 

      while (!finished) 
      { 
       lock (qu) 
        while (qu.Count > 0) 
         yield return qu.Dequeue(); 
       //maybe a thread sleep here? 
      } 

Düzenleme:

 public static IEnumerable<TOutput> ParallelYieldReturn<TSource, TOutput>(this IEnumerable<TSource> source, Func<TSource, TOutput> func) 
     { 
      ConcurrentQueue<TOutput> qu = new ConcurrentQueue<TOutput>(); 
      bool finished = false; 
      AutoResetEvent re = new AutoResetEvent(false); 
      Task.Factory.StartNew(() => 
      { 
       Parallel.ForEach(source, (item) => 
       { 
        qu.Enqueue(func(item)); 
        re.Set(); 
       }); 
       finished = true; 
       re.Set(); 
      }); 

      while (!finished) 
      { 
       re.WaitOne(); 
       while (qu.Count > 0) 
       { 
        TOutput res; 
        if (qu.TryDequeue(out res)) 
         yield return res; 
       } 
      } 
     } 

Edit2: Bunun daha iyi olduğunu düşünüyorum Kısa No katılıyorum cevap. Bu kod işe yaramaz; Verim döngüsünü kıramazsın.