Tại sao việc thêm vào TextBox.Text trong một vòng lặp lại chiếm nhiều bộ nhớ hơn với mỗi lần lặp lại?


82

Câu hỏi ngắn

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

Việc sử dụng MyTextBox.Text += someValuekhiến ứng dụng ngốn bộ nhớ rất lớn và hết bộ nhớ khả dụng sau vài nghìn bản ghi.

Có cách nào hiệu quả hơn để nối văn bản với TextBox.Text180.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 tại sao đây dường như là một ổ nhớ và liệu có cách nào hiệu quả hơn để nối văn bản vào TextBox.


Câu hỏi dài (Bản gốc)

Tôi có một ứng dụng nhỏ đọc danh sách các số ID trong tệp CSV và tạo báo cáo PDF cho từng số. Sau khi mỗi tệp pdf được tạo, tệp ResultsTextBox.Textsẽ đượ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ớ mà ứng dụng đang chiếm dụng đang tăng lên theo cấp số nhân khi thời gian trôi qua. Nó bắt đầu với khoảng 90K, nhưng với khoảng 3000 bản ghi, nó chiếm khoảng 250MB và đến 4000 bản ghi, ứng dụng sẽ chiếm khoảng 500 MB bộ nhớ.

Nếu tôi nhận xét về 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ệc viết ResultsText.Text += someValuelà nguyên nhân khiến nó ăn bộ nhớ.

Câu hỏi của tôi là, tại sao lại như vậy? Cách tốt hơn để nối 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 cần phải nhắc lại rằng ứng dụng là một thứ sử dụng một lần và sẽ không quan trọng rằng sẽ 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 Kết quả TextBox đã 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 về bộ nhớ để gắn dữ liệu vào a TextBox.Textcho các dự án trong tương lai hay không.


7
Bạn có thể thử sử dụng một StringBuilderđể nối văn bản, sau đó, sau khi hoàn thành, hãy gán StringBuildergiá trị cho hộp văn bản.
keyboardP

1
Tôi không biết liệu nó có thay đổi gì không nhưng điều gì sẽ xảy ra nếu bạn có một StringBuilder gắn các 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 textbox.text của bạn bất động sản.
BigL

2
Tại sao bạn khởi tạo mảng đối tượng khi gọi string.Format? Có những quá tải lấy 2 tham số để bạn có thể tránh tạo mảng. Thêm vào đó, khi bạn sử dụng quá tải tham số, mảng được tạo cho bạn đằng sau hậu trường.
ChaosPandion

1
chuỗi concat không nhất thiết là không hiệu quả. Nếu bạn đang nối các chuỗi trên nhiều đơn vị công việc và hiển thị kết quả giữa mỗi đơn vị công việc thì nó sẽ hiệu quả hơn StringBuilder. StringBuilder chỉ thực sự hiệu quả hơn khi bạn xây dựng một chuỗi thông qua một vòng lặp và sau đó chỉ viết ra kết quả ở cuối vòng lặp.
James Michael Hare

3
Tôi sẽ nói, đó là khá máy ấn tượng :-)
James Michael Hare

Câu trả lời:


119

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


1
Từ liên kết MSDN: "Rò rỉ bộ nhớ Nếu bạn có bộ nhớ tăng lên trong ứng dụng của mình 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 văn bản có thể là" rò rỉ "bộ nhớ. Bằng cách sử dụng thuộc tính này, bạn có thể tắt nó và dọn đường để 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 giống như hộp văn bản tiêu chuẩn (tức là với khả năng hoàn tác / làm lại). Trong các trường hợp khó khăn như yêu cầu của OP, nó có thể là một trở ngại. Nếu đa số mọi người sử dụng thì nên để mặc định. Tại sao bạn lại mong đợi một chiếc ốp lưng buộc phải sử dụng chức năng tiêu chuẩn?
keyboardP

1
Ngoài ra, bạn cũng có thể đặt UndoLimitgiá trị thực tế. 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 tính năng hoàn tác.
myermian

14

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

Sẽ rất thú vị khi xem điều này so với IsUndoEnabledcờ từ câu trả lời của keyboardP như thế nào .


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

Trong các hình thức Giành chiến thắng, bạn có một bool CanUndotài sản
imlokesh

9

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


2
Tôi quên đề cập đến vòng lặp chạy trên một sợ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 hộp văn bản, tôi sẽ làm như sau:

  1. Mở một tệp văn bản và truyền các lỗi vào tệp nhật ký để đề phòng.
  2. Sử dụng điều khiển hộp danh sách để trình bày các lỗi để tránh sao chép các chuỗi có thể có khối lượng lớn.

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 những năm trước có thống kê mô tả so sánh các phương pháp thường được sử dụng và (dường như) để nhớ lại rằngstring.Concat đã thắng.

Tuy nhiên, điều tốt nhất tôi có thể tìm là câu hỏi tham khảo này và câu hỏi cụ thể String.Formatso vớiStringBuilder câu hỏi này, đề cập đến việc String.Formatsử dụngStringBuilder nội bộ. Điều này khiến tôi tự hỏi liệu bộ nhớ của bạn có nằm ở nơi khác không.

** dựa trên nhận xét của James, tôi nên đề cập rằng tôi không bao giờ thực hiện định dạng chuỗi nặng, vì tôi tập trung vào phát triển dựa trên web. *


Tôi đồng ý, đôi khi mọi người thường nói "luôn luôn sử dụng X cuz X là tốt nhất" thường là đơn giản hóa quá mức. 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 mục đích của nó (nghe có vẻ ngớ ngẩn, tôi biết, nhưng nó đúng). Tôi sử dụng concat khi tôi nối các 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 tầm thường (đệ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.
James Michael Hare

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

Ồ không, tôi chỉ đồng ý với quan điểm chung của bạn rằng concat thường tốt nhất cho những chuỗi đơn giản. 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 này sang phiên bản khác nếu BCL thay đổi, do đó, việc gắn bó với cấu trúc đúng logic sẽ dễ bảo trì hơn và thường hoạt động tốt hơn cho các tác vụ của nó. Tôi thực sự đã có một bài viết trên blog cũ, nơi tôi đã so sánh ba ở đây: geekswithblogs.net/BlackRabbitCoder/archive/2010/05/10/...
James Michael Hare

Duly đã lưu ý - chỉ muốn chắc chắn - và câu trả lời đã được chỉnh sửa để tôi đủ điều kiện sử dụng từ "luôn luôn".
jwheron

3

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

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

Cách tốt nhất là hiển thị một mẫu dữ liệu hoặc một chỉ báo tiến trình.

Khi bạn muốn kết xuất nó tại Người dùng kém, hãy cập nhật hàng loạt chuỗi. Không người dùng nào có thể xác định 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 nhóm 50 người.


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

2

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


2

Sử dụng StringBuilder như được đề xuất. Cố gắng ước tính kích thước chuỗi cuối cùng rồi 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 phép gán ví dụ: textbox.text = sb.ToString ();

Theo dõi các hoạt động xuyên luồng như trên. Tuy nhiên hãy sử dụng BeginInvoke. Không cần chặn luồng nền trong khi giao diện người dùng cập nhật.


1

A) Giới thiệu: đã được đề 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à công việc một lần, hãy sử dụng kiến ​​trúc x64 để ở trong giới hạn 2Gb.


1

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


0

Một cái gì đó chưa được đề cập là ngay cả khi bạn đang thực hiện thao tác trong luồng nền, bản cập nhật của phần tử giao diện người dùng sẽ xảy ra trên chính luồng chính (dù sao trong WinForms).

Khi cập nhật hộp văn bản của bạn, bạn có bất kỳ mã nào giống

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 đang bị tắc nghẽn bởi Bản cập nhật giao diện người dùng.

Tôi khuyên bạn nên sử dụng nền tảng của bạn sử dụng StringBuilder như đã nói ở 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 định kỳ để 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.


0

Bạn nói trí nhớ phát triển theo cấp số nhân. Không, đó là tăng trưởng bậc hai , tức là tăng trưởng đa thức, không gây ấn tượng mạnh như tăng trưởng theo cấp số nhân.

Bạn đang tạo chuỗi chứa số lượng mục sau:

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

Với việc n = 180,000bạn nhận được tổng phân bổ bộ nhớ cho 16,200,090,000 items, tức là 16.2 billion items! Bộ nhớ này sẽ không được cấp phát 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ộ thu gom rác)!

Ngoài ra, hãy nhớ 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 đi cùngn^2 !

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.