2017-07-21 21 views
5

Ứng dụng máy chủ của chúng tôi có một số phương thức, được gọi theo thứ tự lặp qua kết quả 20M và chuyển đổi nó. Mỗi phương pháp trong đường ống này lưu trữ bản sao dữ liệu 200+ megabyte, với RAM có thể dự đoán trước và tác động hiệu suất GC.Mẫu để chia nhỏ C# bằng cách sử dụng khối để bật lập trình chức năng

Mỗi phương pháp sau một mô hình tương tự:

public HugeCollection1 Step1 (SomeType sourceData) 
{ 
    var transformed = new List<RowType>; 
    using (var foo = InitializeSomethingExpensive(sourceData)) 
    { 
     foreach (var row in foo) 
     { 
      transformed.Add (TransformRow(row)); 
     } 
    } 
    return transformed; 
} 

Sau đó, các phương pháp này được gọi là trong một đường ống, ví dụ

var results1 = Step1(sourceData); 
var results2 = Step2(results1); 
var results3 = Step3(results2); 
... 
var finalResults = StepN (resultsNMinus1); 
return finalResults; // final results 

Tôi muốn biến điều này thành giải pháp chức năng hơn lặp qua dữ liệu nguồn gốc mà không bao giờ giữ toàn bộ tập dữ liệu trong RAM. Tôi muốn kết thúc với Danh sách kết quả cuối cùng mà không có bất kỳ bộ sưu tập trung gian nào.

Nếu không có thiết lập bắt buộc ở mỗi giai đoạn của đường ống, thì giải pháp sẽ đơn giản: chỉ cần chạy từng chuyển đổi cho mỗi hàng và chỉ lưu trữ kết quả cuối cùng.

var transformed = new List<SmallResult>; 
// TODO: How to set up and ensure teardown of the *other* pipeline steps? 
using (var foo = InitializeSomethingExpensive(sourceData)) 
{ 
    foreach (var row in foo) 
    { 
     object result = row; 
     foreach (var step in Pipeline) 
     { 
      result = step.Transform (result); 
     } 
     transformed.Add (result as SmallResult); 
    } 
} 
return transformed; 

Nhưng ngày nay, mỗi bước đường ống riêng biệt này có quy trình thiết lập và xé riêng được thực thi thông qua khối using.

Mô hình tốt để cấu trúc lại từng phương pháp đường ống này để mã thiết lập/teardown được đảm bảo là gì? Trong pseudo-code, tôi muốn kết thúc với điều này:

  1. cài đặt tất cả các bước
  2. Vòng qua mỗi hàng
  3. Chuyển hàng qua từng bước
  4. End loop
  5. Cleanup tất cả các bước , đảm bảo rằng việc dọn dẹp luôn xảy ra
  6. Trả lại (nhỏ) kết quả

Nó không được ca ngợi ctical để kết hợp tất cả các khối sử dụng vào một phương thức duy nhất vì mã trong mỗi bước này dài và được chia sẻ và tôi không muốn lặp lại mã được chia sẻ đó trong một phương thức.

Tôi biết tôi có thể thay thế thủ công khối using bằng try/finally, nhưng làm điều đó theo cách thủ công cho nhiều tài nguyên có vẻ khó khăn hơn mức cần thiết.

Có giải pháp đơn giản hơn hay không, ví dụ: sử dụng usingyield cùng một cách thông minh? Hoặc có một triển khai lớp "đa sử dụng" có sẵn giúp quá trình thiết lập/tách rời được phối hợp dễ dàng (ví dụ: hàm tạo của nó chấp nhận danh sách các hàm trả về IDisposable và việc thực thi Dispose() của nó sẽ đảm bảo rằng mọi thứ được làm sạch)?

Có vẻ như đây là một mô hình mà ai đó thông minh hơn tôi đã tìm ra, vì vậy hãy hỏi tại đây trước khi phát minh lại bánh xe.

+0

Tôi có một thời gian khó dịch mối quan tâm đa sử dụng của bạn vì tôi không thấy gì nhiều hơn việc sử dụng khối trong mã của bạn. Năng suất có giá trị đáng ngờ vì người gọi không cần phải di chuyển đến cuối chuỗi, đó là nơi bạn sẽ tự nhiên gọi là Vứt bỏ. – hoodaticus

+1

Mỗi bước đường ống có yêu cầu 'foo' riêng của mình (là bản sao của dữ liệu nguồn không?) – Blorgbeard

+0

Xác định xác định là một trong những lĩnh vực mà C++ pwns quản lý ngôn ngữ. – hoodaticus

Trả lời

3

Tôi không chắc chắn lý do tại sao bạn đang tạo rất nhiều đối tượng dùng một lần (bạn có thể xóa sạch chúng với các phương pháp yieldable) nhưng bạn có thể tạo ra một phương pháp khuyến nông để làm sạch mô hình này cho bạn

public static class ToolsEx 
{ 
    public static IEnumerable<T> EnumerateAndDispose<X, T>(this X input, 
             Func<X, IEnumerable<T>> func) 
     where X : IDisposable 
    { 
     using (var mc = input) 
      foreach (var i in func(mc)) 
       yield return i; 
    } 
} 

bạn có thể sử dụng nó thích này ...

var query = from x in new MyClass(0, 0, 2).EnumerateAndDispose(i => i) 
      from y in new MyClass(1, x, 3).EnumerateAndDispose(i => i) 
      select new 
      { 
       x, 
       y, 
      }; 

foreach (var i in query) 
    Console.WriteLine(i); 

... ra ...

{ x = 0, y = 0 } 
{ x = 0, y = 1 } 
{ x = 0, y = 2 } 
Disposed: 1/0 
{ x = 1, y = 0 } 
{ x = 1, y = 1 } 
{ x = 1, y = 2 } 
Disposed: 1/1 
Disposed: 0/0 

đây là một pipeli Ví dụ ne với Aggregate ...

var query = from x in new MyClass(0, 0, 2).EnumerateAndDispose(i => i) 
      let r = new MyClass(1, x, 3).EnumerateAndDispose(i => i) 
               .Aggregate(x, (a, i) => (a + i) * 2) 
      select new 
      { 
       x, 
       r, 
      }; 

... và kết quả ...

Disposed: 1/0 
{ x = 0, r = 8 } 
Disposed: 1/1 
{ x = 1, r = 16 } 
Disposed: 0/0 

... lớp thử nghiệm ví dụ như ...

public class MyClass : IEnumerable<int>, IDisposable 
{ 

    public MyClass(int set, int set2, int size) 
    { 
     this.Size = size; 
     this.Set = set; 
     this.Set2 = set2; 
    } 

    public IEnumerator<int> GetEnumerator() 
    { 
     foreach (var i in Enumerable.Range(0, this.Size)) 
      yield return i; 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    public void Dispose() 
    { 
     Console.WriteLine("Disposed: {0}/{1}", this.Set, this.Set2); 
    } 

    public int Size { get; private set; } 
    public int Set { get; private set; } 
    public int Set2 { get; private set; } 
} 
+0

btw, sau khi bạn có điều này, bạn có thể thay thế đường ống của mình bằng '.Aggragate (...)' –

Các vấn đề liên quan