2009-02-07 84 views
7

Có cách nào để làm foreach lặp lại kiểu trên các bảng tính song song trong C#? Đối với danh sách có thể lập chỉ mục, tôi biết một danh sách có thể sử dụng vòng lặp for thường xuyên lặp lại một int trong phạm vi chỉ mục, nhưng tôi thực sự thích foreach đến for vì một số lý do.Lặp song song trong C#?

Điểm thưởng nếu nó hoạt động trong C# 2.0

+0

wouldn' một vòng lặp for là giải pháp đơn giản hơn, ngắn hơn, dễ đọc hơn thay vì phản hồi Kết hợp bên dưới? lý do của bạn cho thích foreach trong trường hợp này là gì – Gishu

+0

Cũng song song lặp lại chỉ nổi lên trong Ruby 1.9 vì vậy tôi muốn đặt cược vào nó không được trong C# như của bây giờ .. LISP đã có nó mặc dù :) – Gishu

+0

Tôi không chắc tôi hiểu câu hỏi đúng. Bạn đang cố gắng lặp qua nhiều bảng số song song, hoặc bạn đang cố gắng lặp qua một số đếm, xử lý các mục khác nhau song song? –

Trả lời

9

Trả lời ngắn gọn, không. foreach chỉ hoạt động trên một danh sách tại một thời điểm.

Tuy nhiên, nếu bạn kết hợp các bảng kê song song của mình thành một số duy nhất, bạn có thể foreach qua kết hợp. Tôi không biết về bất kỳ dễ dàng, được xây dựng trong phương pháp để làm điều này, nhưng sau đây nên làm việc (mặc dù tôi đã không kiểm tra nó):

public IEnumerable<TSource[]> Combine<TSource>(params object[] sources) 
{ 
    foreach(var o in sources) 
    { 
     // Choose your own exception 
     if(!(o is IEnumerable<TSource>)) throw new Exception(); 
    } 

    var enums = 
     sources.Select(s => ((IEnumerable<TSource>)s).GetEnumerator()) 
     .ToArray(); 

    while(enums.All(e => e.MoveNext())) 
    { 
     yield return enums.Select(e => e.Current).ToArray(); 
    } 
} 

Sau đó, bạn có thể foreach qua trở lại đếm được:

foreach(var v in Combine(en1, en2, en3)) 
{ 
    // Remembering that v is an array of the type contained in en1, 
    // en2 and en3. 
} 
+0

Tại sao bạn chọn đối tượng [] thay vì IEnumerable làm loại tham số? Nó sẽ loại bỏ ngoại lệ, phải không? – mafu

+0

Có ít nhất một phiên bản của ngôn ngữ C# không biên dịch thông số với bất kỳ thứ gì khác ngoài đối tượng []. Xem xét câu trả lời này là năm tuổi, tôi đoán phiên bản duy nhất tôi đã sử dụng tại thời điểm đó là một phiên bản không hoạt động với 'params IEnumerable [] sources'. Những ngày này, tất nhiên tôi sẽ sử dụng cách gõ rõ ràng hơn. Tôi cũng có thể sử dụng phương pháp mở rộng 'IEnumerable .Zip' cho hai số đếm - khá chắc chắn điều này cũng không tồn tại 5 năm trước. – Zooba

0

Điều này có phù hợp với bạn không?

public static class Parallel 
{ 
    public static void ForEach<T>(IEnumerable<T>[] sources, 
            Action<T> action) 
    { 
     foreach (var enumerable in sources) 
     { 
      ThreadPool.QueueUserWorkItem(source => { 
       foreach (var item in (IEnumerable<T>)source) 
        action(item); 
      }, enumerable); 
     } 
    } 
} 

// sample usage: 
static void Main() 
{ 
    string[] s1 = { "1", "2", "3" }; 
    string[] s2 = { "4", "5", "6" }; 
    IEnumerable<string>[] sources = { s1, s2 }; 
    Parallel.ForEach(sources, s => Console.WriteLine(s)); 
    Thread.Sleep(0); // allow background threads to work 
} 

Đối với C# 2.0, bạn cần chuyển đổi biểu thức lambda ở trên thành đại biểu.

Lưu ý: Phương pháp tiện ích này sử dụng chủ đề nền. Bạn có thể muốn sửa đổi nó để sử dụng các chủ đề tiền cảnh, và có lẽ bạn sẽ muốn đợi cho đến khi tất cả các chủ đề kết thúc. Nếu bạn làm điều đó, tôi đề nghị bạn tạo các chủ đề sources.Length - 1 và sử dụng chuỗi thực thi hiện tại cho nguồn cuối cùng (hoặc trước tiên).

(Tôi ước gì có thể bao gồm chờ đợi đề để kết thúc trong mã của tôi, nhưng tôi xin lỗi mà tôi không biết làm thế nào để làm điều đó được nêu ra. Tôi đoán bạn nên sử dụng một WaitHandleThread.Join().)

11

BlockingCollection của .NET 4 làm cho việc này trở nên dễ dàng. Tạo một BlockingCollection, trả về .GetConsumingEnumerable() trong phương thức đếm được. Sau đó, foreach chỉ đơn giản là thêm vào bộ sưu tập chặn.

Ví dụ:

private BlockingCollection<T> m_data = new BlockingCollection<T>(); 

public IEnumerable<T> GetData(IEnumerable<IEnumerable<T>> sources) 
{ 
    Task.Factory.StartNew(() => ParallelGetData(sources)); 
    return m_data.GetConsumingEnumerable(); 
} 

private void ParallelGetData(IEnumerable<IEnumerable<T>> sources) 
{ 
    foreach(var source in sources) 
    { 
     foreach(var item in source) 
     { 
      m_data.Add(item); 
     }; 
    } 

    //Adding complete, the enumeration can stop now 
    m_data.CompleteAdding(); 
} 

Hy vọng điều này sẽ hữu ích. BTW tôi posted a blog about this đêm qua

Andre

+1

Điều này sẽ được đánh dấu là đúng bây giờ .net 4.0 là ra –

+0

Điều này không trả lời câu hỏi, đó là về liệt kê nhiều số đếm với nhau. –

+0

Bạn hoàn toàn chính xác Arthur, tôi sẽ sửa chữa nó – Andre

3

tôi đã viết một thực hiện EachParallel() từ thư viện Parallel .NET4. Nó tương thích với .NET 3.5: Parallel ForEach Loop in C# 3.5 Cách sử dụng:

string[] names = { "cartman", "stan", "kenny", "kyle" }; 
names.EachParallel(name => 
{ 
    try 
    { 
     Console.WriteLine(name); 
    } 
    catch { /* handle exception */ } 
}); 

Thực hiện:

/// <summary> 
/// Enumerates through each item in a list in parallel 
/// </summary> 
public static void EachParallel<T>(this IEnumerable<T> list, Action<T> action) 
{ 
    // enumerate the list so it can't change during execution 
    list = list.ToArray(); 
    var count = list.Count(); 

    if (count == 0) 
    { 
     return; 
    } 
    else if (count == 1) 
    { 
     // if there's only one element, just execute it 
     action(list.First()); 
    } 
    else 
    { 
     // Launch each method in it's own thread 
     const int MaxHandles = 64; 
     for (var offset = 0; offset < list.Count()/MaxHandles; offset++) 
     { 
      // break up the list into 64-item chunks because of a limitiation    // in WaitHandle 
      var chunk = list.Skip(offset * MaxHandles).Take(MaxHandles); 

      // Initialize the reset events to keep track of completed threads 
      var resetEvents = new ManualResetEvent[chunk.Count()]; 

      // spawn a thread for each item in the chunk 
      int i = 0; 
      foreach (var item in chunk) 
      { 
       resetEvents[i] = new ManualResetEvent(false); 
       ThreadPool.QueueUserWorkItem(new WaitCallback((object data) => 
       { 
        int methodIndex = (int)((object[])data)[0]; 

        // Execute the method and pass in the enumerated item 
        action((T)((object[])data)[1]); 

        // Tell the calling thread that we're done 
        resetEvents[methodIndex].Set(); 
       }), new object[] { i, item }); 
       i++; 
      } 

      // Wait for all threads to execute 
      WaitHandle.WaitAll(resetEvents); 
     } 
    } 
} 
1

Nếu bạn muốn dính vào những điều cơ bản - Tôi viết lại câu trả lời hiện đang chấp nhận một cách đơn giản hơn:

public static IEnumerable<TSource[]> Combine<TSource> (this IEnumerable<IEnumerable<TSource>> sources) 
    { 
     var enums = sources 
      .Select (s => s.GetEnumerator()) 
      .ToArray(); 

     while (enums.All (e => e.MoveNext())) { 
      yield return enums.Select (e => e.Current).ToArray(); 
     } 
    } 

    public static IEnumerable<TSource[]> Combine<TSource> (params IEnumerable<TSource>[] sources) 
    { 
     return sources.Combine(); 
    }