2012-01-04 36 views
81

Câu hỏi ngắnTại sao phụ thêm vào TextBox.Text trong vòng lặp mất nhiều bộ nhớ hơn với mỗi lần lặp?

Tôi có vòng lặp chạy 180.000 lần. Vào cuối mỗi lần lặp lại, nó được cho là gắn kết quả vào một TextBox, được cập nhật theo thời gian thực.

Sử dụng MyTextBox.Text += someValue đang khiến ứng dụng ăn một lượng bộ nhớ khổng lồ và hết bộ nhớ sau một vài nghìn bản ghi.

Có cách nào hiệu quả hơn để nối văn bản vào TextBox.Text 180.000 lần không?

Chỉnh sửa Tôi thực sự không quan tâm đến kết quả của trường hợp cụ thể này, tuy nhiên tôi muốn biết lý do tại sao điều này dường như là bộ nhớ và nếu có cách hiệu quả hơn để nối văn bản vào TextBox.


Long (Original) Câu hỏi

Tôi có một ứng dụng nhỏ mà đọc một danh sách các số ID trong một tập tin CSV và tạo ra một báo cáo PDF cho mỗi một. Sau khi mỗi tệp pdf được tạo, ResultsTextBox.Text được nối với Số ID của báo cáo đã được xử lý và nó đã được xử lý thành công. Quá trình chạy trên một chuỗi nền, do đó, ResultsTextBox được cập nhật theo thời gian thực khi các mục được xử lý

Tôi hiện đang chạy ứng dụng với 180.000 số ID, tuy nhiên bộ nhớ ứng dụng đang tăng lên theo cấp số nhân theo thời gian đi bằng. Nó bắt đầu khoảng 90K, nhưng khoảng 3000 hồ sơ nó chiếm khoảng 250MB và 4000 hồ sơ ứng dụng chiếm khoảng 500 MB bộ nhớ.

Nếu tôi nhận xét bản cập nhật cho hộp văn bản kết quả, bộ nhớ vẫn tương đối cố định ở khoảng 90K, vì vậy tôi có thể giả định rằng viết ResultsText.Text += someValue là những gì gây ra nó để ăn bộ nhớ.

Câu hỏi của tôi là, tại sao điều này? Cách tốt nhất để thêm dữ liệu vào TextBox.Text không ăn bộ nhớ là gì?

Mã của tôi trông như thế này:

try 
{ 
    report.SetParameterValue("Id", id); 

    report.ExportToDisk(ExportFormatType.PortableDocFormat, 
     string.Format(@"{0}\{1}.pdf", new object[] { outputLocation, id})); 

    // ResultsText.Text += string.Format("Exported {0}\r\n", id); 
} 
catch (Exception ex) 
{ 
    ErrorsText.Text += string.Format("Failed to export {0}: {1}\r\n", 
     new object[] { id, ex.Message }); 
} 

Cũng nên đáng nói đến là ứng dụng là một điều một lần và nó không quan trọng mà nó sẽ mất một vài giờ (hoặc vài ngày :)) để tạo tất cả các báo cáo. Mối quan tâm chính của tôi là nếu nó đạt đến giới hạn bộ nhớ hệ thống, nó sẽ ngừng chạy.

Tôi ổn với việc để dòng cập nhật Hộp văn bản kết quả nhận xét để chạy điều này, nhưng tôi muốn biết liệu có cách nào hiệu quả hơn để bổ sung dữ liệu vào TextBox.Text cho các dự án trong tương lai hay không.

+7

Bạn có thể thử sử dụng 'StringBuilder' để chắp thêm văn bản, sau khi hoàn thành, gán giá trị' StringBuilder' vào hộp văn bản. – keyboardP

+0

@keyboardP Tôi quên đề cập đến vòng lặp chạy trên chuỗi nền và Hộp văn bản kết quả được cập nhật theo thời gian thực – Rachel

+1

Tôi không biết liệu nó có thay đổi được gì hay không nhưng nếu bạn có một StringBuilder gắn thêm Id mới và bạn sẽ sử dụng một thuộc tính được cập nhật với giá trị mới của trình tạo chuỗi và Liên kết điều này với thuộc tính textbox.text của bạn. – BigL

Trả lời

119

Tôi nghi ngờ lý do sử dụng bộ nhớ quá lớn là do hộp văn bản duy trì ngăn xếp để người dùng có thể hoàn tác/làm lại bản văn. Tính năng đó dường như không được yêu cầu trong trường hợp của bạn, vì vậy hãy thử đặt IsUndoEnabled thành sai.

+1

Từ liên kết MSDN: "Memory leak Nếu bạn có bộ nhớ tăng lên trong ứng dụng của bạn vì bạn đặt giá trị từ mã rất thường xuyên, ngăn xếp hoàn tác của khối chữ có thể là một "rò rỉ" của bộ nhớ. Bằng cách sử dụng thuộc tính này, bạn có thể vô hiệu hóa nó và dọn dẹp cách rò rỉ bộ nhớ. " – wave

+33

Phần lớn thời gian, người dùng và nhà phát triển mong đợi hộp văn bản hoạt động như hộp văn bản chuẩn (nghĩa là với khả năng hoàn tác/làm lại). Trong các trường hợp cạnh như các yêu cầu của OP, nó có thể chứng minh là một sự cản trở.Nếu đa số mọi người sử dụng nó, thì nó sẽ được mặc định. Tại sao bạn mong đợi một trường hợp cạnh để buộc chức năng tiêu chuẩn trở thành lựa chọn tham gia? – keyboardP

+1

Ngoài ra, bạn cũng có thể đặt 'UndoLimit' thành giá trị thực. Giá trị mặc định là -1 cho biết ngăn xếp không giới hạn. Zero (0) cũng sẽ tắt hoàn tác. –

9

Không nối trực tiếp vào thuộc tính văn bản. Sử dụng StringBuilder để thêm vào, sau đó khi hoàn tất, hãy đặt .text thành chuỗi đã hoàn thành từ chuỗi builder

+2

Tôi quên đề cập đến vòng lặp chạy trên chuỗi nền và kết quả được cập nhật theo thời gian thực – Rachel

5

Thay vì sử dụng một hộp văn bản tôi sẽ làm như sau:

  1. Mở ra một tập tin văn bản và dòng lỗi vào một tập tin bản ghi chỉ trong trường hợp.
  2. Sử dụng điều khiển hộp danh sách để biểu thị các lỗi để tránh sao chép các chuỗi có khả năng lớn.
1

A) Giới thiệu: đã đề cập, sử dụng StringBuilder

B) điểm: không cập nhật quá thường xuyên, tức là

DateTime dtLastUpdate = DateTime.MinValue; 

while (condition) 
{ 
    DoSomeWork(); 
    if (DateTime.Now - dtLastUpdate > TimeSpan.FromSeconds(2)) 
    { 
     _form.Invoke(() => {textBox.Text = myStringBuilder.ToString()}); 
     dtLastUpdate = DateTime.Now; 
    } 
} 

C) Nếu đó là một thời gian công việc, kiến ​​trúc sử dụng x64 để ở trong giới hạn 2Gb.

4

Cá nhân, tôi luôn sử dụng string.Concat *. Tôi nhớ đọc một câu hỏi ở đây trên Stack Overflow năm trước đã có thống kê hồ sơ so sánh các phương pháp thường được sử dụng, và (dường như) để nhớ rằng string.Concat thắng.

Tuy nhiên, điều tốt nhất tôi có thể tìm thấy là this reference question và câu hỏi String.Format vs. StringBuilder cụ thể này, đề cập đến rằng String.Format sử dụng số StringBuilder nội bộ. Điều này làm cho tôi tự hỏi nếu hog bộ nhớ của bạn nằm ở nơi khác.

** dựa trên nhận xét của James, tôi nên đề cập đến mà tôi không bao giờ làm định dạng chuỗi nặng, như tôi đã tập trung vào phát triển dựa trên web. *

+0

Tôi đồng ý, đôi khi mọi người nhận được trong rut của nói rằng "luôn luôn sử dụng X cuz X là tốt nhất" mà thường là một oversimplification. Có rất nhiều sự tinh tế giữa string.Concat(), string.Format() và StringBuilder. Quy tắc ngón tay cái của tôi là sử dụng từng cái mà nó dành cho nó (nghe có vẻ ngớ ngẩn, tôi biết, nhưng nó đúng). Tôi sử dụng concat khi tôi tham gia chuỗi (và sau đó sử dụng kết quả ngay lập tức), tôi sử dụng Định dạng khi tôi thực hiện định dạng chuỗi không nhỏ (đệm, định dạng số, v.v.) và StringBuilder để tạo chuỗi trong một vòng lặp được sử dụng ở cuối vòng lặp. –

+0

@JamesMichaelHare, điều đó hợp lý với tôi; bạn có gợi ý rằng việc sử dụng 'string.Format' /' StringBuilder' thích hợp hơn ở đây không? – jwiscarson

+0

Ồ không, tôi đã đồng ý với điểm chung của bạn rằng concat thường là tốt nhất cho các chuỗi đơn giản concats. Vấn đề với "quy tắc ngón tay cái" là chúng có thể thay đổi từ phiên bản .NET sang phiên bản nếu thay đổi BCL, do đó gắn bó với cấu trúc chính xác hợp lý là dễ bảo trì hơn và thường hoạt động tốt hơn cho nhiệm vụ của nó. Tôi thực sự đã có một bài đăng trên blog cũ hơn khi tôi so sánh ba bài đăng này tại đây: http://geekswithblogs.net/BlackRabbitCoder/archive/2010/05/10/c-string-compares-and-concatenations.aspx –

3

lẽ xem xét lại TextBox? Một ListBox giữ chuỗi Items có thể sẽ hoạt động tốt hơn.

Nhưng vấn đề chính có vẻ là yêu cầu, Hiển thị 180.000 mục không thể nhắm vào người dùng (người), cũng không thay đổi nó trong "Thời gian thực".

Cách thích hợp hơn là hiển thị mẫu dữ liệu hoặc chỉ báo tiến trình.

Khi bạn muốn đổ nó vào người dùng, cập nhật chuỗi hàng loạt kém. Không người dùng nào có thể bỏ qua nhiều hơn 2 hoặc 3 thay đổi mỗi giây. Vì vậy, nếu bạn sản xuất 100/giây, hãy tạo các nhóm 50.

+0

Cảm ơn Henk. Đây là một điều một lần nên tôi đã lười biếng khi viết nó. Tôi muốn một số loại đầu ra trực quan để biết tình trạng là gì, và tôi muốn có khả năng lựa chọn văn bản và một ScrollBar. Tôi cho rằng tôi có thể đã sử dụng một ScrollViewer/Label, nhưng TextBoxes có ScrollBarrs được xây dựng trong. Tôi không mong đợi nó sẽ gây ra vấn đề :) – Rachel

1

StringBuilder trong ViewModel sẽ tránh xáo trộn chuỗi lộn xộn và liên kết nó với MyTextBox.Text. Kịch bản này sẽ tăng hiệu suất nhiều lần và giảm mức sử dụng bộ nhớ.

2

Một số câu trả lời đã ám chỉ đến nó, nhưng không ai hoàn toàn tuyên bố điều đó thật bất ngờ. Chuỗi không thay đổi có nghĩa là Chuỗi không thể sửa đổi sau khi được tạo. Vì vậy, mỗi khi bạn nối vào một String hiện có, một đối tượng String mới cần phải được tạo ra. Bộ nhớ kết hợp với đối tượng String đó rõ ràng cũng cần phải được tạo ra, có thể tốn kém khi các chuỗi của bạn trở nên lớn hơn và lớn hơn. Ở trường đại học, tôi đã từng phạm sai lầm nghiệp dư của việc ghép chuỗi trong một chương trình Java đã nén Huffman mã hóa. Khi bạn ghép nối một lượng lớn văn bản, Chuỗi nối có thể thực sự làm tổn thương bạn khi bạn có thể đã sử dụng StringBuilder đơn giản, như một số ở đây đã đề cập đến.

0

Điều gì đó chưa được đề cập là ngay cả khi bạn đang thực hiện thao tác trong chuỗi nền, bản cập nhật của phần tử UI chính nó cũng xảy ra trên chính chuỗi (trong WinForms).

Khi cập nhật textbox của bạn, làm bạn có bất kỳ mã trông giống như

if(textbox.dispatcher.checkAccess()){ 
    textbox.text += "whatever"; 
}else{ 
    textbox.dispatcher.invoke(...); 
} 

Nếu vậy, thì op nền của bạn chắc chắn được bottlenecked bởi UI Update.

Tôi khuyên bạn nên sử dụng nền tảng StringBuilder như đã lưu ý ở trên, nhưng thay vì cập nhật hộp văn bản mỗi chu kỳ, hãy thử cập nhật nó theo chu kỳ thường xuyên để xem nó có tăng hiệu suất cho bạn hay không.

CHỈNH SỬA LƯU Ý: chưa sử dụng WPF.

2

Sử dụng StringBuilder như được đề xuất. Hãy thử để ước tính kích thước chuỗi cuối cùng sau đó sử dụng số đó khi khởi tạo StringBuilder. StringBuilder sb = new StringBuilder (estSize);

Khi cập nhật TextBox chỉ cần sử dụng gán ví dụ: textbox.text = sb.ToString();

Xem hoạt động của chuỗi chéo như trên. Tuy nhiên sử dụng BeginInvoke. Không cần phải chặn chuỗi nền trong khi cập nhật giao diện người dùng.

14

Sử dụng TextBox.AppendText(someValue) thay vì TextBox.Text += someValue. Thật dễ dàng để bỏ lỡ vì nó trên TextBox, không phải TextBox.Text. Giống như StringBuilder, điều này sẽ tránh tạo các bản sao của toàn bộ văn bản mỗi khi bạn thêm một cái gì đó.

Sẽ rất thú vị khi xem cách so sánh này với cờ IsUndoEnabled từ câu trả lời của keyboardP.

+0

Trong trường hợp các hình thức Windows, đây là giải pháp tốt nhất, như các hình thức cửa sổ không có TextBox.IsUndoEnabled – BrDaHa

+0

Trong biểu mẫu Win, bạn có thuộc tính 'bool CanUndo' – imlokesh

0

Bạn nói bộ nhớ tăng theo cấp số nhân. Không, đó là số quadratic growth, tức là sự tăng trưởng đa thức, không phải là kịch tính như tăng trưởng theo cấp số nhân.

Bạn đang tạo chuỗi giữ số sau các hạng mục:

1 + 2 + 3 + 4 + 5 ... + n = (n^2 + n) /2. 

Với n = 180,000 bạn nhận được tổng số phân bổ bộ nhớ cho 16,200,090,000 items, ví dụ: 16.2 billion items! Bộ nhớ này sẽ không được cấp cùng một lúc, nhưng nó là rất nhiều công việc dọn dẹp cho GC (bộ gom rác)!

Ngoài ra, lưu ý rằng chuỗi trước đó (đang phát triển) phải được sao chép vào chuỗi mới 179,999 lần. Tổng số byte được sao chép cũng đi kèm với n^2!

Như những người khác đã đề xuất, hãy sử dụng ListBox thay thế. Ở đây bạn có thể nối thêm chuỗi mới mà không cần tạo chuỗi lớn. A StringBuild không giúp ích gì, vì bạn cũng muốn hiển thị kết quả trung gian.

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