VB のたまご

作成日: 2017/04/02, 更新日: 2017/04/02



メモリリーク1

  •  前回の続きです。

  • スポンサーリンク


  •  今回のサンプルでは、前回見たモーダル画面を表示するアプリケーションのうち、ビューモデルの生成と破棄の部分に注目したいと思います。 まずは、ベースとなるプログラムを以下に表示します。これらは「Livet WPF4.5 MVVM アプリケーション」より作成したものです。

  • Namespace Models
        Public Class Model ' Friend → Public
            Inherits NotificationObject
    
    #Region "Name変更通知プロパティ"
            Private _Name As String = "default"
    
            Public Property Name() As String
                Get
                    Return _Name
                End Get
                Set(ByVal value As String)
                    If (_Name = value) Then Return
                    _Name = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
    #Region "Age変更通知プロパティ"
            Private _Age As Integer = 32
    
            Public Property Age() As Integer
                Get
                    Return _Age
                End Get
                Set(ByVal value As Integer)
                    If (_Age = value) Then Return
                    _Age = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Shared Function ValueEquals(self As Model, other As Model) As Boolean
    
                If (self Is Nothing) OrElse (other Is Nothing) Then
                    Return False
                End If
    
                If self.Name <> other.Name Then
                    Return False
                End If
    
                If self.Age <> other.Age Then
                    Return False
                End If
    
                Return True
    
            End Function
    
        End Class
    End Namespace
    

    Imports LivetWPFApplication3.Models
    
    Namespace ViewModels
        Public Class MainWindowViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model = New Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
    #Region "TestCommand"
            Private _TestCommand As ViewModelCommand
    
            Public ReadOnly Property TestCommand() As ViewModelCommand
                Get
                    If _TestCommand Is Nothing Then
                        _TestCommand = New ViewModelCommand(AddressOf Test)
                    End If
                    Return _TestCommand
                End Get
            End Property
    
            Private Sub Test()
    
                Dim doClean As Action(Of String) = Sub(s) Console.WriteLine($"{s} : {(GC.GetTotalMemory(True) / 1024).ToString("N")} KB")
    
                ' 何回も生成と破棄を繰り返して、メモリリークが発生するか確認
                doClean("開始")
    
                For i As Integer = 0 To 10
    
                    For k As Integer = 0 To 1000000
    
                        '' 【+=】だけ
                        '' メモリリークが発生する
                        'Using vm = New Test1ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【+=】の後【-=】
                        '' メモリリークは発生しない(即時解放)
                        'Using vm = New Test2ViewModel(Me.Person)
    
                        '    vm.MyDispose()
    
                        'End Using
    
                        '' 【System.Windows.WeakEventManager(Of TSource, TEventArgs)】
                        '' メモリリークは発生しない(遅延開放。ある程度溜まって、必要になったら解放)
                        '' 弱いイベントと呼ばれている。解放処理をしなくてもメモリリークは発生しない
                        '' ただし、一生懸命メモリをやりくりしているからか、ボタンクリックやプログラム終了処理の反応が遅くなる、応答無しでは無い。
                        '' でも普通、100万回もイベント処理を登録しないので、実際はそんなに遅くないと思われる
                        'Using vm = New Test3ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【System.ComponentModel.PropertyChangedEventManager】のうち、AddHandler メソッドを利用。
                        '' 以前からある? AddListener メソッドの簡易版。
                        '' IWeakEventListener インターフェースを継承したクラスを準備しなくてもいいので楽
                        '' メモリリークは発生しない(遅延開放。ある程度溜まって、必要になったら解放)
                        '' 弱いイベントと呼ばれている。解放処理をしなくてもメモリリークは発生しない
                        '' ただし、一生懸命メモリをやりくりしているからか、ボタンクリックやプログラム終了処理の反応が遅くなる、応答無しでは無い。
                        '' でも普通、100万回もイベント処理を登録しないので、実際はそんなに遅くないと思われる
                        'Using vm = New Test4ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【System.ComponentModel.PropertyChangedEventManager】のうち、AddListener メソッドを利用。
                        '' メモリリークは発生しない(遅延開放。ある程度溜まって、必要になったら解放)
                        '' 弱いイベントと呼ばれている。解放処理をしなくてもメモリリークは発生しない
                        '' ただし、一生懸命メモリをやりくりしているからか、ボタンクリックやプログラム終了処理の反応が遅くなる、応答無しでは無い。
                        '' でも普通、100万回もイベント処理を登録しないので、実際はそんなに遅くないと思われる
                        'Using vm = New Test5ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【Livet.EventListeners.PropertyChangedEventListener】+【Dispose】
                        '' メモリリークは発生しない(即時解放)
                        '' =PropertyChangedEventListener だけならメモリリークは発生する
                        'Using vm = New Test6ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【Livet.EventListeners.EventListener】+【Dispose】
                        '' メモリリークは発生しない(即時解放)
                        '' =EventListener だけならメモリリークは発生する
                        'Using vm = New Test7ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【Livet.EventListeners.WeakEvents.PropertyChangedWeakEventListener】+【Dispose】
                        '' メモリリークは発生しない(即時解放)
                        '' =PropertyChangedWeakEventListener だけならメモリリークは発生する
                        '' WeakEvent と命名されているので、解放しなくてもいけると思うのだが、100万回はやりすぎだから?使い方間違えてる?
                        'Using vm = New Test8ViewModel(Me.Person)
    
                        'End Using
    
                        '' 【Livet.EventListeners.WeakEvents.LivetWeakEventListener】+【Dispose】
                        '' メモリリークは発生しない(即時解放)
                        '' =LivetWeakEventListener だけならメモリリークは発生する
                        '' WeakEvent と命名されているので、解放しなくてもいけると思うのだが、100万回はやりすぎだから?使い方間違えてる?
                        'Using vm = New Test9ViewModel(Me.Person)
    
                        'End Using
    
                    Next
    
                Next
    
                doClean("終了")
    
            End Sub
    #End Region
    
            Public Sub Initialize()
            End Sub
    
    
        End Class
    End Namespace
    

    <Window x:Class="Views.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
            xmlns:v="clr-namespace:LivetWPFApplication3.Views"
            xmlns:vm="clr-namespace:LivetWPFApplication3.ViewModels"
            Title="MainWindow" Height="350" Width="525">
        
        <Window
            <vm:MainWindowViewModel/>
        </Window
        
        <i:Interaction
            
            <!--WindowのContentRenderedイベントのタイミングでViewModelのInitializeメソッドが呼ばれます-->
            <i:EventTrigger EventName="ContentRendered">
                <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="Initialize"/>
            </i:EventTrigger>
    
            <!--Windowが閉じたタイミングでViewModelのDisposeメソッドが呼ばれます-->
            <i:EventTrigger EventName="Closed">
                <l:DataContextDisposeAction/>
            </i:EventTrigger>
    
            <!--WindowのCloseキャンセル処理に対応する場合は、WindowCloseCancelBehaviorの使用を検討してください-->
    
        </i:Interaction
    
        <StackPanel>
    
            <Button Content="テスト" Command="{Binding TestCommand}" />
    
        </StackPanel>
    
    </Window>
    

  •  このサンプルでは、ボタンを押した後に呼ばれる MainWindowViewModel クラスの Test メソッドが注目ポイントです。 実際に画面を出す処理はせず、ただビューモデルの生成と破棄にとどめておいています。 いくつかのまとまりでコメントアウトしていますので、1つ1つ見ていきましょう。

  • スポンサーリンク



1つ目のビューモデル

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    
    Namespace ViewModels
        Public Class Test1ViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Sub New(person As Model)
    
                Me.Person = person
                AddHandler Me.Person.PropertyChanged, AddressOf Me.Person_PropertyChanged
                Me.Person.Name = "jiro"
                Me.Person.Age = 45
    
            End Sub
    
            Private Sub Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    
                Dim value = Me.Person.GetType().GetProperty(e.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{e.PropertyName} = {value} に変更されました")
    
            End Sub
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

  •  これを実行するのが以下の部分です。

  • ' 【+=】だけ
    ' メモリリークが発生する
    Using vm = New Test1ViewModel(Me.Person)
    
    End Using
    

  •  これを何回か実行すると OutOfMemoryException 例外エラーが発生します。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。


2つ目のビューモデル

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    
    Namespace ViewModels
        Public Class Test2ViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Sub New(person As Model)
    
                Me.Person = person
                AddHandler Me.Person.PropertyChanged, AddressOf Me.Person_PropertyChanged
                Me.Person.Name = "jiro"
                Me.Person.Age = 45
    
            End Sub
    
            Public Sub MyDispose()
    
                RemoveHandler Me.Person.PropertyChanged, AddressOf Me.Person_PropertyChanged
    
            End Sub
    
            Private Sub Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    
                Dim value = Me.Person.GetType().GetProperty(e.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{e.PropertyName} = {value} に変更されました")
    
            End Sub
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

  •  これを実行するのが以下の部分です。

  • ' 【+=】の後【-=】
    ' メモリリークは発生しない(即時解放)
    Using vm = New Test2ViewModel(Me.Person)
    
        vm.MyDispose()
    
    End Using
    

  •  これを実行してもメモリリークは発生しません。イベント解除しているからですね。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。

  •  次回に続きます。