都内で働くSEの技術的なひとりごと / Technical soliloquy of System Engineer working in Tokyo

都内でサラリーマンやってます。SQL Server を中心とした (2023年からは Azure も。) マイクロソフト系(たまに、OSS系などマイクロソフト以外の技術も...)の技術的なことについて書いています。日々の仕事の中で、気になったことを技術要素関係なく気まぐれに選んでいるので記事内容は開発言語、インフラ等ばらばらです。なお、当ブログで発信、発言は私個人のものであり、所属する組織、企業、団体等とは何のかかわりもございません。ブログの内容もきちんと検証して使用してください。英語の勉強のため、英語の

WPF と Silverlight メモリリーク発生原因について少し調べてみた

 SilverlightWPF のメモリーリークに関する情報を探してみました。ここにいい記事がありました。詳しい記事は、PDFをダウンロードしたほうが理解が容易だと思います。

 

 これはありがち。昔はよくやりました。イベントは解放しないといけません。

Unregistered Events

A classic leak common to all .NET applications, and a common oversight by developers. If you create an event handler to handle events occurring in some other object then, if you don't clear the link when you've finished, an unwanted strong reference will be left behind.

Say I am subscribing to an OnPlaced event on my Order class, and say this code executes on a button click:

 

Order newOrder=new Order
(“EURUSD”, DealType.Buy, Price ,PriceTolerance, TakeProfit, StopLoss);

newOrder.OnPlaced+=OrderPlaced;
m_PendingDeals.Add(newOrder);

 

When the price is right, an Order completes and calls the OnPlaced event, which is handled by the OrderPlacedmethod;

 

void OrderPlace(Order placedOrder)
{
m_PendingDeals.Remove(placedOrder);
}

 

The OrderPlaced event handler still holds a reference to the Order object from when we subscribed to the OnPlacedevent, and that reference will keep the Order object alive even though we have removed it from the collection. It's soeasy to make this mistake.

 

 ProperttyDescriptorは、プロパティまわりを拡張できるので、便利な機能ですね。プロパテイの変更検知とか汎用的にかけていいですね。 ValueChangedイベントで、子のオブジェクトから、親オブジェクトにバインドしている場合にメモリーリークが発生するようですね。

BindingOperations.ClearBinding(DependencyObject target, DependencyProperty dp)でバインドをクリアすることができるんですね。へー。

Databinding

You read that right; data binding, the thing you rely on, can cause memory leaks. Strictly speaking it's actually the way you use it that causes the leak. If you have a child object that data binds to a property of its parent, a memory leak can occur. An example of this is shown below.

 

<Grid Name="mainGrid">
        
    <TextBlock Name=”txtMainText” 
    Text="{Binding ElementName=mainGrid, Path=Children.Count}" />
    
</Grid>

 

The condition will only occur if the bound property is a PropertyDescriptor property, as Children.Count is. This is because, in order to detect when a PropertyDescriptor property changes, the framework has to subscribe to theValueChanged event, which in turn sets up a strong reference chain.

 

 静的イベントも解放しましょう。Unregistered Eventと同じですね。

Static Events

Subscribing to an event on a static object will set up a strong reference to any objects handling that event. Statics, one referenced, remain for the duration of the app domain execution, and therefore so do all their references. And strong references preventing garbage collection are just memory leaks by another name.

 

 追加したバインディグは、きちんと削除しましょう。CommandBindings.Removeです。

Command Binding

Command binding allows you to separate common application commands (and their invocation) from where they are handled. You can write your classes to handle specific commands, or not, and even indicate if those commands can be executed. The problem, much like the others we've seen, occurs when these bindings leave unwanted strong references lying around.

Say I'm setting up some code within a child window to handle when Cut is executed within the parent mainWindow. I first create a CommandBinding, and then simply add it to the parent windows CommandBindings collection:

The good news is that the .NET framework now takes care of freeing objects for you, and is also ultra-cautious. It works out whether it thinks a particular object is going to be needed while your program runs, and it will only release that object if it can completely guarantee that that object is not going to be needed again.

 

CommandBinding cutCmdBinding = new CommandBinding
(ApplicationCommands.Cut, OnMyCutHandler, OnCanICut);
            
 mainWindow.main.CommandBindings.Add(cutCmdBinding);

...
void OnMyCutHandler (object target, ExecutedRoutedEventArgs e)
{
    
    MessageBox.Show("You attempted to CUT");
}

void OnCanICut (object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

 

This code leaks because we are leaving a strong reference in the mainWindow.main.CommandBindings object pointing to the child. As a result, even when the child closes, it will still remain in memory.

 

 timer.Stop(); timer = null; すべき。しかし、常時動かさなければいけない場合はどうすればいいんでしょう?適宜とめないといけないんですかね。

Dispatchertimer Leak

Improper use of the DispatcherTimer will cause a memory leak. The code below creates a new DispatcherTimer within a user control, and to make it easier to see the leak, I have also added a byte array called myMemory to make the leak more obvious.

 

public byte[] myMemory = new byte[50 * 1024 * 1024];

System.Windows.Threading.DispatcherTimer _timer = new System.Windows
.Threading.DispatcherTimer();
int count = 0;

private void MyLabel_Loaded(object sender, RoutedEventArgs e)
{

_timer.Interval = TimeSpan.FromMilliseconds(1000);

_timer.Tick += new EventHandler(delegate(object s, EventArgs ev)

{
count++;
textBox1.Text = count.ToString();
});

_timer.Start();
}

 

On my main window I am adding an instance of the UserControl to a StackPanel (after removing children first) on a button click, which will leak memory with every click. Tracing it backwards using ANTS profiler shows that theUserControl as the source of the leak.

The problem is, once again, a reference being held, this time by the Dispatcher, which holds a collection of liveDispatcherTimers. The strong reference from the collection keeps each UserControl alive, and therefore leaks 

 

 そんなことでリークするんですね。.IsUndoEnabled=false;は無理だけど、Limitは許容できますね。普通に使っている限りは発生しないものでしょう。

Textbox undo leak

This is not really a leak; it is intended behavior, but it's important to know it's there. TextBoxes have built-in undo functionality, enabling a user to undo their changes to a text box. To allow for that, WPF maintains a stack of recent changes, and when you use a memory profiler you can clearly see a build up of data on this undo stack. This isn't a major problem unless your app is updating large strings to text boxes over many iterations.  The main reason to note this behavior is because it can often show up on memory profile traces and there is no point being distracted by it.

 Unregistered Event などは流石にわすれることはないですが(以外とそうでもない?)、Textbox undo leak などは盲点ですね。メモリーリークを起こさないように気をつけてコーディングしましょう。