TextBox.Text Leaking Memory in WPF Application

2020-02-14 10:03发布

问题:

I have an application that does a large amount of number crunching on an array of numbers. I have it set to every 100,000 operations to display the status of those numbers. This is just sort of a visual indicator to the operator that the application is still processing, and how close it is to completed.

After running the application for several hours, it crashes with an OutOfMemoryExeption. I ran CLRProfiler on it, and found that the leak is occurring in the TextBox that shows the current state of the array... the application builds ~4K in Char[] every time the interface updates, the stack trace for each bit of hung memory is the same.

Is this a bug in WPF, or is there something that I don't know I should be doing to prevent this from happening? I've not seen any other references to this. Since this is a pretty processor intensive operation anyway, I'd prefer not to have to destroy and rebuild the TextBox if I don't have to just to display the current state of the array.

This is the only memory leak that I can find, but due to the sheer number of operations required, it's actually a big problem for the application, even if I simply reduce the frequency of the interface update. If you need more information, I'll assist if at all possible, but please understand that due to what this application does, I can't post hardly any source code, and I'll have to anonymize anything (removing any indication of the application's purpose) that I CAN post.

When run through the CLR Profiler, which DRASTICALLY reduces performance, the following identical trace occurs every 2 seconds (remember: 100,000 operations) and the memory is never deallocated. When it's running without a profiler attached, the time between updates is < 1 second.

The trace from one update ("Who Allocated") is:

<root> :  3.7 kB    (100.00%)
 WpfApplication0.App::Main static void ():  3.7 kB    (100.00%)
  System.Windows.Application::Run int32 ():  3.7 kB    (100.00%)
   System.Windows.Application::Run int32 (System.Windows.Window):  3.7 kB    (100.00%)
    System.Windows.Application::RunInternal int32 (System.Windows.Window):  3.7 kB    (100.00%)
     System.Windows.Application::RunDispatcher Object (Object):  3.7 kB    (100.00%)
      System.Windows.Threading.Dispatcher::Run static void ():  3.7 kB    (100.00%)
       System.Windows.Threading.Dispatcher::PushFrame static void (System.Windows.Threading.DispatcherFrame):  3.7 kB    (100.00%)
        System.Windows.Threading.Dispatcher::PushFrameImpl void (System.Windows.Threading.DispatcherFrame):  3.7 kB    (100.00%)
         NATIVE FUNCTION ( UNKNOWN ARGUMENTS ):  3.7 kB    (100.00%)
          MS.Win32.HwndSubclass::SubclassWndProc int_ptr (int_ptr int32 int_ptr int_ptr):  3.7 kB    (100.00%)
           System.Windows.Threading.Dispatcher::Invoke Object (System.Windows.Threading.DispatcherPriority  Object):  3.7 kB    (100.00%)
            System.Windows.Threading.Dispatcher::InvokeImpl Object (System.Windows.Threading.DispatcherPriority   Object bool):  3.7 kB    (100.00%)
             System.Windows.Threading.Dispatcher::WrappedInvoke Object ( Object bool ):  3.7 kB    (100.00%)
              System.Windows.Threading.ExceptionWrapper::TryCatchWhen Object (Object  Object bool ):  3.7 kB    (100.00%)
               System.Windows.Threading.ExceptionWrapper::InternalRealCall Object ( Object bool):  3.7 kB    (100.00%)
                MS.Win32.HwndSubclass::DispatcherCallbackOperation Object (Object):  3.7 kB    (100.00%)
                 MS.Win32.HwndWrapper::WndProc int_ptr (int_ptr int32 int_ptr int_ptr bool&):  3.7 kB    (100.00%)
                  System.Windows.Threading.Dispatcher::WndProcHook int_ptr (int_ptr int32 int_ptr int_ptr bool&):  3.7 kB    (100.00%)
                   System.Windows.Threading.Dispatcher::ProcessQueue void ():  3.7 kB    (100.00%)
                    System.Windows.Threading.DispatcherOperation::Invoke Object ():  3.7 kB    (100.00%)
                     System.Threading.ExecutionContext::Run static void (System.Threading.ExecutionContext System.Threading.ContextCallback Object):  3.7 kB    (100.00%)
                      System.Threading.ExecutionContext::RunInternal static void (System.Threading.ExecutionContext System.Threading.ContextCallback Object):  3.7 kB    (100.00%)
                       System.Threading.ExecutionContext::runTryCode static void (Object):  3.7 kB    (100.00%)
                        System.Windows.Threading.DispatcherOperation::InvokeInSecurityContext static void (Object):  3.7 kB    (100.00%)
                         System.Windows.Threading.DispatcherOperation::InvokeImpl void ():  3.7 kB    (100.00%)
                          System.Windows.Threading.Dispatcher::WrappedInvoke(1) Object ( Object bool ):  3.7 kB    (100.00%)
                           System.Windows.Threading.ExceptionWrapper::TryCatchWhen(1) Object (Object  Object bool ):  3.7 kB    (100.00%)
                            System.Windows.Threading.ExceptionWrapper::InternalRealCall(1) Object ( Object bool):  3.7 kB    (100.00%)
                             System.Delegate::DynamicInvokeImpl Object (Object[]):  3.7 kB    (100.00%)
                              System.Reflection.RuntimeMethodInfo::Invoke Object (Object System.Reflection.BindingFlags System.Reflection.Binder Object[] System.Globalization.CultureInfo bool):  3.7 kB    (100.00%)
                               System.RuntimeMethodHandle::InvokeMethodFast Object (Object Object[] System.Signature System.Reflection.MethodAttributes System.RuntimeTypeHandle):  3.7 kB    (100.00%)
                                WpfApplication0.Window1::UpdateUI void ():  3.7 kB    (100.00%)
                                 System.Windows.Controls.TextBox::set_Text void (String):  3.7 kB    (100.00%)
                                  System.Windows.DependencyObject::SetValue void (System.Windows.DependencyProperty Object):  3.7 kB    (100.00%)
                                   System.Windows.DependencyObject::SetValueCommon void (System.Windows.DependencyProperty Object System.Windows.PropertyMetadata bool System.Windows.OperationType bool):  3.7 kB    (100.00%)
                                    System.Windows.DependencyObject::UpdateEffectiveValue System.Windows.UpdateResult (System.Windows.EntryIndex System.Windows.DependencyProperty System.Windows.PropertyMetadata System.Windows.EffectiveValueEntry System.Windows.EffectiveValueEntry& bool System.Windows.OperationType):  3.7 kB    (100.00%)
                                     System.Windows.DependencyObject::NotifyPropertyChange void (System.Windows.DependencyPropertyChangedEventArgs):  3.7 kB    (100.00%)
                                      System.Windows.Controls.TextBox::OnPropertyChanged void ():  3.7 kB    (100.00%)
                                       System.Windows.FrameworkElement::OnPropertyChanged void ():  3.7 kB    (100.00%)
                                        System.Windows.DependencyObject::OnPropertyChanged void (System.Windows.DependencyPropertyChangedEventArgs):  3.7 kB    (100.00%)
                                         System.Windows.Controls.TextBox::OnTextPropertyChanged static void ( ):  3.7 kB    (100.00%)
                                          System.Windows.Documents.TextContainer::DeleteContentInternal void (System.Windows.Documents.TextPointer System.Windows.Documents.TextPointer):  3.7 kB    (100.00%)
                                           System.Windows.Documents.TextTreeUndo::CreateDeleteContentUndoUnit static System.Windows.Documents.TextTreeDeleteContentUndoUnit (System.Windows.Documents.TextContainer System.Windows.Documents.TextPointer System.Windows.Documents.TextPointer):  3.7 kB    (100.00%)
                                            System.Windows.Documents.TextTreeDeleteContentUndoUnit::.ctor void (System.Windows.Documents.TextContainer System.Windows.Documents.TextPointer System.Windows.Documents.TextPointer):  3.7 kB    (100.00%)
                                             System.Windows.Documents.TextTreeDeleteContentUndoUnit::CopyContent ContentContainer (System.Windows.Documents.TextTreeNode System.Windows.Documents.TextTreeNode):  3.7 kB    (100.00%)
                                              System.Windows.Documents.TextTreeDeleteContentUndoUnit::CopyTextNode System.Windows.Documents.TextTreeNode (System.Windows.Documents.TextTreeTextNode System.Windows.Documents.TextTreeNode ContentContainer&):  3.7 kB    (100.00%)
                                               System.Char [] :  3.7 kB    (100.00%)

and the code generating a UI update is:

        List<int> arraystatus = new List<int>(displayarray.ToArray());

        StringBuilder s = new StringBuilder();

        int i = 1;

        foreach (int item in arraystatus)
        {
            s.Append(i.ToString() + ":\t" + item.ToString() + (i % 8 == 0 ? "\n" : "\t"));
            i++;
        }

        txtStatus.Text = s.ToString();

        arraystatus = null;
        s = null;

回答1:

From the documentation of the TextBoxBase.UndoLimit property:

The number of actions stored in the undo queue. The default is –1, which means the undo queue is limited to the memory that is available.

You found that limit. Set it to a reasonably small value.

It otherwise doesn't normally make a lot of sense to display logging information in a TextBox. It is really meant to allow the user to enter text. Perhaps TextBlock is a better choice.


UPDATE: in .NET 4.5, the default of -1 was changed to 100 to avoid this kind of runaway memory usage.



回答2:

+1 for Hans.

Also, you're using StringBuilder. Awesome.

However, you're concatenating FOUR strings for every item in arraystatus (which could be 100k for all I know) for each time your UI is updated, making StringBuilder pretty much pointless.

Try

s.Append(i.ToString());
s.Append(":\t");
s.Append(item.ToString());
s.Append(i % 8 == 0 ? "\n" : "\t"));

or, even better, try converting this to a single call of AppendFormat.