【问题标题】:Garbage collection on a closed-over object封闭对象上的垃圾收集
【发布时间】:2012-11-19 19:20:40
【问题描述】:

注意以下代码来自asp.net。


如果我有下面的(写得不好)代码

AmazonS3 s3Client = Amazon.AWSClientFactory.CreateAmazonS3Client();

// ...
// details elided
// ...

BackgroundWorker worker = new BackgroundWorker();

worker.DoWork += new DoWorkEventHandler((s, args) =>
{
    s3Client.PutObject(titledRequest);
});

new Thread(() => worker.RunWorkerAsync()).Start();

垃圾收集器是否足够聪明,永远不会收集 s3Client 对象,直到后台工作人员完成它?


请注意,我在线程内启动后台工作程序只是为了修复在我直接启动后台工作程序时在 asp.net 中引发的恼人错误。

【问题讨论】:

  • 没错。这就是 GC 的用途。直到线程丢失所有引用,s3client 将被保留!
  • 你为什么要开始一个新线程只是为了运行RunWorkerAsync()。这是一个异步方法开始;该方法的内容只是创建一个新线程,对其进行一些配置,然后启动它。无需在后台线程中执行此操作;你这样做会增加毫无意义的开销。您也不需要定义使用 lambdas 时使用的委托,您可以完全删除 new DoWorkEventHandler;它将根据上下文进行暗示。
  • @Servy 感谢您提供关于不需要定义委托的提示。另一方面,当您启动 backgroundWorker 时,asp.net 会抛出这个令人难以置信的烦人错误。从四处寻找似乎不是一个简单的解决方法,所以我使用了这个俗气的解决方法。
  • @AdamRackis 你没有提到这是在 ASP 环境中。在这方面,BackgroundWorker 根本不是为在 ASP 环境中工作而设计的。它设计用于在 winform/WPF 类型的环境中工作。您应该直接在新线程中执行长时间运行的工作,而不使用BackgroundWorker 或使用Task 来启动新的长时间运行的任务。
  • @Servy - 我在问题末尾的注释中确实提到了它,但无论如何,在 asp.net 环境中,您是否说 GC 会以不同的方式工作,并且在上面的代码中,S3客户实际上可能会被处置?

标签: c# asp.net c#-4.0 garbage-collection


【解决方案1】:

是的,它会的。编译器将为您生成一个新类,其中包含您在闭包中引用的每个局部变量的字段。闭包体将被发送到该类的方法中,并且包含函数中的所有局部变量都将被编译器重写为引用该闭包对象上的字段。

所有这些魔法都发生在编译时;运行时不需要知道任何关于它的信息。由于运行时已经足够聪明,不会收集作为委托目标的对象,因此闭包引用的本地对象的生命周期可以保证延长到生成的委托对象的生命周期。

为了说明,编译器将输出如下内容:

[System.Runtime.CompilerServices.CompilerGeneratedAttribute]
internal class ClosureImplementation // See note 1
{
    public AmazonS3 s3Client;

    public void Method(object s, EventArgs args)
    {
        s3Client.PutObject(titledRequest); // See note 2
    }
}

然后,在您的方法中,它被发出:

ClosureImplementation closure = new ClosureImplementation();
closure.s3Client = Amazon.AWSClientFactory.CreateAmazonS3Client();

// ...

worker.DoWork += closure.Method;

注意事项:

  1. 生成的类的名称由编译器选择; ClosureImplementation 只是一个例子。
  2. 我没有足够的上下文知道titledRequest 的来源,所以我故意没有说明编译器将如何处理它。

【讨论】:

  • 哇 - 很好的答案,谢谢。我喜欢你的回答here btw :)
【解决方案2】:

简而言之,是的。

您正在为您的事件处理程序使用 lambda,并且您正在关闭一个局部变量。这意味着您正在使用来自定义 lambda 的块之外的范围内的变量。这意味着,当编译器将 lambda 转换为“真实”方法时(在类内部,具有真实名称和一个对象实例和所有)它将包含对您作为该类的字段关闭的局部变量的引用。这将使该对象保持在范围内,直到 lambda 不再在任何地方被引用或在任何地方执行。

【讨论】:

  • 请注意,这也适用于匿名委托,而不仅仅是 lambda 表达式。
  • @cdhowie 确实如此,但我认为 lambdas 会使匿名委托过时。真的不再需要它们了,而且我看到很少有人继续使用它们。
  • Lambda 没有任何表示“匹配预期的参数类型并且不使用它们”的语法。我仍然使用delegate { ... },因为没有等效的 lambda 语法——使用 lambda,您必须提供参数列表。此外,如果可能,lambdas 将更喜欢基于Expression 的重载方法,否则将退回到基于委托的重载。在某些情况下可能需要强制使用委托重载而不是Expression 重载,因此在这种情况下您也可以使用匿名委托语法。
猜你喜欢
  • 2010-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多