在静态方法中使用匿名 Lamba 订阅事件会导致内存泄漏吗?

Posted

技术标签:

【中文标题】在静态方法中使用匿名 Lamba 订阅事件会导致内存泄漏吗?【英文标题】:Can subscribing to events with anonymous lamba in static method cause memory leak? 【发布时间】:2020-01-31 14:00:36 【问题描述】:

我的理解是,事件的订阅者(消费者)总是有被泄露的风险(如果生产者的寿命更长)。如果我在静态方法中使用匿名 lambda 函数订阅(非静态)事件,如果我希望 lambda 与生产者一起存在,我不应该取消订阅?

有一个variant of the question(lambda 事件订阅会造成内存泄漏吗?)和this answer,引用:

另外,那个 lambda 表达式没有使用任何变量,所以它可能会通过一个没有目标的静态方法来实现......我假设你关心的真实情况有一个更有趣的 lambda 主体.

我将此解释为,如果 lambda 表达式使用来自目标 (this) 的变量,您可能必须取消订阅,但在静态方法中,this 不存在,因此问题。

我想到的具体代码来自this answer(见下文)。该答案的 cmets 建议您必须取消订阅以避免内存泄漏,但这真的是真的吗?究竟泄露了什么? Another answer 对于试图处理取消订阅的同一个问题,实际上添加了潜在的内存泄漏(通过将事件处理程序存储在可能无法清理的静态字典中)。

private static void BindableColumnsPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)

    DataGrid dataGrid = source as DataGrid;
    ObservableCollection<DataGridColumn> columns = e.NewValue as ObservableCollection<DataGridColumn>;

    // There should be no need to unsubscribe to e.OldValue?

    dataGrid.Columns.Clear();

    if (columns == null)
    
        return;
    

    foreach (DataGridColumn column in columns)
    
        dataGrid.Columns.Add(column);
    

    // This event handler will not keep the columns alive, and the lambda will only be alive as long as the columns is alive?
    columns.CollectionChanged += (sender, e2) =>
    
        NotifyCollectionChangedEventArgs ne = e2 as NotifyCollectionChangedEventArgs;
        if (ne.Action == NotifyCollectionChangedAction.Reset)
        
            // Clear dataGrid.Columns
            ...
        
        else if (ne.Action == NotifyCollectionChangedAction.Add)
        
            // Add to dataGrid.Columns
            ...
        
        else if (ne.Action == NotifyCollectionChangedAction.Move)
        
            ...
        
        else if (ne.Action == NotifyCollectionChangedAction.Remove)
        
         ...
        
        else if (ne.Action == NotifyCollectionChangedAction.Replace)
        
          ...
        
    ;

【问题讨论】:

这不仅与this 有关,还与您的 lambda 捕获 的内容有关。 @dymanoid 这很有趣。所以在上面的例子中,dataGrid 在 lambda 中使用。这是否意味着dataGridcolumns 都会使 lambda 保持活动状态? 【参考方案1】:

考虑到您引用的答案和有关泄漏的评论,您必须注意以下几点。

在视图模型中有一个ObservableCollection&lt;DataGridColumn&gt; 类型的对象。在 WPF 中,视图模型的寿命可能比它们的视图长(例如,您可以为同一个视图模型切换不同的视图,或者您可以始终保持视图模型处于活动状态,并且仅在需要时显示相应的视图,例如,考虑一个可隐藏的工具窗口) .

该视图模型对象通过 lambda 获取事件订阅:

columns.CollectionChanged += (sender, e2) =>  /* ... */ ;

lambda 本身捕获一个视图元素 - dataGrid

// lambda body

    // ...
    dataGrid.Columns.Clear();

现在,你有一个强大的引用链:columns -> lambda 对象 -> dataGrid

这意味着 - 只要 columns 对象存在,dataGrid 对象就会存在。如上所述,这是一个视图模型对象,它可以在应用程序运行的整个过程中存在。因此,dataGrid 将继续存在,即使相应的视图不再可见并且没有其他引用。

这就是他们所说的泄漏。

【讨论】:

很好的答案,这对我来说点击了:“columns -> lambda object -> dataGrid”,一个可能需要一个新问题的后续行动:lamba 是否仅引用 @987654332 @ 因为它在 lambda 中使用?还是 lambda 捕获对其词法范围内所有变量的引用?即:如果我在 lambda 正文中停止使用dataGrid,是否还会因为它存在于词法范围内而存在泄漏? C# 中的 lambda 只捕获那些在 lambda 主体中显式使用的变量。与 C++ 相比,您可以指示 lambda 捕获范围内的所有内容。因此,如果 lambda 不会使用(捕获)dataGrid,则不会有dataGrid 泄漏。

以上是关于在静态方法中使用匿名 Lamba 订阅事件会导致内存泄漏吗?的主要内容,如果未能解决你的问题,请参考以下文章

[C/C++11语法]_[初级]_[lamba 表达式介绍]

通过匿名委托取消订阅事件[重复]

Java语言中四种内部类(成员内部类,局部内部类,静态内部类,匿名内部类)在实际使用中有啥好处?

复习 Python 匿名函数 内建函数

JAVA学习之局部内部类,匿名内部类,静态内部类

内部类和匿名内部类的用法