是否存在忽略 IDisposable.Dispose 的时间?

发布于 2024-08-30 08:21:35 字数 807 浏览 5 评论 0原文

当然,一旦我们不需要 IDisposable 对象,我们就应该对它们调用 Dispose()(这通常只是“using”语句的范围)。如果我们不采取这种预防措施,那么可能会发生一些不好的事情,从微妙的到令人震惊的。

但是进程终止前的“最后一刻”又如何呢?如果您的 IDisposables 在该时间点尚未被显式处置,那么它是否不再重要了?我问这个问题是因为 CLR 下的非托管资源由内核对象表示 - 并且 win32 进程终止将释放所有非托管资源/内核对象。换句话说,进程终止后,任何资源都不会保持“泄漏”状态(无论是否对延迟的 IDisposables 调用 Dispose())。

谁能想到一种情况,进程终止仍然会留下泄漏的资源,仅仅因为 Dispose() 没有在一个或多个 IDisposables 上显式调用?

请不要误解这个问题:我并不是想证明忽略 IDisposables 是合理的。这个问题只是技术理论问题。

编辑: 那么在 Linux 上运行的 mono 又如何呢?进程终止在清理不受管理的“泄漏”方面是否同样“可靠”?

最新编辑:虽然 IDisposables 可能存在“其他用途”,但我的重点是严格关注资源泄漏。我听到过两个答案:(1)如果您的进程拒绝终止,则会发生泄漏;(2)是的,即使进程终止,资源也可能泄漏。我当然同意第(1)项,尽管它超出了我所追求的范围。否则,第(2)项正是我正在寻找的,但我无法摆脱这只是猜测的感觉。 Jeffrey Richter(“Windows via C/C++”)解释说,(正常)终止的 Win32 进程不会留下泄漏或孤立的资源。为什么包含 CLR 的进程会改变这一点?哪里有文档、具体示例或理论场景可以证明使用 CLR 时 Win32 进程清理功能会受到损害?

Certainly we should call Dispose() on IDisposable objects as soon as we don't need them (which is often merely the scope of a "using" statement). If we don't take that precaution then bad things, from subtle to show-stopping, might happen.

But what about "the last moment" before process termination? If your IDisposables have not been explicitly disposed by that point in time, isn't it true that it no longer matters? I ask because unmanaged resources, beneath the CLR, are represented by kernel objects - and the win32 process termination will free all unmanaged resources / kernel objects anyway. Said differently, no resources will remain "leaked" after the process terminates (regardless if Dispose() was called on lingering IDisposables).

Can anyone think of a case where process termination would still leave a leaked resource, simply because Dispose() was not explicitly called on one or more IDisposables?

Please do not misunderstand this question: I am not trying to justify ignoring IDisposables. The question is just technical-theoretical.

EDIT: And what about mono running on Linux? Is process termination there just as "reliable" at cleaning up unmanaged "leaks?"

LATE EDIT: Although "other uses" may exist for IDisposables, my focus is strictly on resource leaks. I've heard two answers: (1) if your process refuses to terminate, you will have a leak and (2) yes, resources can leak even if the process terminates. I certainly agree with item (1), though it is just outside the scope of what I'm after. Otherwise, item (2) is exactly what i'm looking for, but I can't shake the feeling it is just a guess. Jeffrey Richter ("Windows via C/C++") explains that a (gracefully) terminated Win32 process will not leave leaked or orphaned resources. Why would a process containing the CLR change that? Where is the documentation, specific example, or theoretical scenario that gives credance to the idea that the Win32 process cleanup capability is compromised when using the CLR?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

超可爱的懒熊 2024-09-06 08:21:35

从技术上讲,这完全取决于 IDisposable 的作用。它已被用于很多事情,而不仅仅是非托管资源。

例如,在开发 Outlook 应用程序时,我构建了 Outlook API 的一个很好的小抽象。附件作为流使用时特别烦人,因为您需要将其保存到临时文件中,使用它,然后清理它。

所以我的抽象看起来像这样:

OutlookMailItem mailItem = blah;
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) {
   // work with stream
}

当在 AttachmentStream 上调用 Dispose 时,它​​所基于的临时文件被删除。在这种情况下,如果不调用 Dispose,临时文件将永远不会被清理。我在启动时有一个过程来查找这些孤立的文件,但我想我应该将此作为一个例子。

实际上,几乎所有包装某种套接字、句柄或事务的 IDisposable 实现都会在进程终止时被操作系统简单地清除。但显然这就像福利。如果可以的话避免它。

Technically speaking, it all depends on what the IDisposable does. It has been used for a lot of things, not just unmanaged resources.

For example, when working on an Outlook application I had built a nice little abstraction of the Outlook API. Attachments were particularly annoying to work with as streams because you needed to save it out to a temp file, work with it, then clean it up.

So my abstraction looked something like this:

OutlookMailItem mailItem = blah;
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) {
   // work with stream
}

When Dispose was called on the AttachmentStream, the temp file it was based on was deleted. In this case, if Dispose was not called, the temp file would never be cleaned up. I had a process at startup to look for these orphaned files but I figured I'd present this as an example.

In reality, nearly all IDisposable implementations which wrap some kind of socket, handle, or transaction will simply be cleaned up by the operating system upon process termination. But obviously that's like welfare. Avoid it if you can.

烟若柳尘 2024-09-06 08:21:35

在协作关闭期间,AppDomain 被卸载,这会导致所有终结器执行:

来自 Object.Finalize 文档:

在应用程序域关闭期间,会自动对未免于最终化的对象调用 Finalize,即使是那些仍可访问的对象。

因此,只要满足两个条件,您就可以安全地关闭:

  • 每个仍然存在的 IDisposable 对象都有一个正确实现的终结器(对于 Framework 类来说是这样,对于 less- 可能不是这样)值得信赖的图书馆);

  • 这实际上是一种协作关闭,而不是非正常关闭,例如硬进程终止、控制台应用程序中的 Ctrl-C 或 Environment.FailFast

如果这两个条件中的任何一个不满足,您的应用程序可能会占用全局非托管资源(例如互斥体),这实际上会泄漏。因此,如果可以的话,最好尽早调用 Dispose大多数,您可以依靠 CLR 和对象终结器来为您完成这项工作,但安全总比后悔好。

During a cooperative shutdown, the AppDomain is unloaded, which causes all finalizers to execute:

From Object.Finalize documentation:

During shutdown of an application domain, Finalize is automatically called on objects that are not exempt from finalization, even those that are still accessible.

So you're safe on shutdown as long as two criteria are met:

  • Every IDisposable object that's still alive has a correctly-implemented finalizer (true of Framework classes, may not be true of less-trustworthy libraries); and

  • It's actually a cooperative shutdown, and not an abnormal shutdown such as a hard process termination, Ctrl-C in a console app, or Environment.FailFast.

If either of these two criteria aren't met, it is possible that your application is holding onto global unmanaged resources (such as a mutex), which actually will leak. Therefore, it's always better to call Dispose early if you can. Most of the time, you can rely on the CLR and object finalizers to do this work for you, but better to be safe than sorry.

星光不落少年眉 2024-09-06 08:21:35

我经常发现自己处理的一件事是串行端口——现在,虽然当程序停止时应该释放串行端口,但当串行端口被另一个进程占用时,其他程序无法访问该串行端口。因此,如果您的进程拒绝终止,那么您就占用了串行端口。如果用户尝试重新启动您的程序,但前一个进程的僵尸版本仍然保留在串行端口上,那可能会非常糟糕。

是的,我的程序之前发现自己处于僵尸状态,然后客户抱怨该程序不再工作,因为该程序在重新启动时无法连接到串行端口。结果要么引导用户在任务管理器中终止进程,要么让它们重新启动,这两种任务都不是特别用户友好的任务。

One thing that I often find myself disposing is serial ports-- now, while a serial port should be freed when the program lets up, other programs can't access the serial port while it's being held by another process. So, if your process refuses to die, then you're tying up a serial port. That can be really bad if the user tries to restart your program, but a zombie version of the previous process is still holding on to the serial port.

And yes, I've had my program find itself in a zombie state before, and then the customer complain that the program isn't working anymore because the program fails to attach to the serial port on restart. The result is either walking the user through killing a process in the task manager or having them reboot, neither of which is a particularly user-friendly task.

じее 2024-09-06 08:21:35

您编写自定义代码来释放一次性类中的对象。它供您编写代码以释放非托管和托管代码。

这就是为什么我们需要 Dispose 函数。所有内存释放都不是自动的,就像您在非托管代码的情况下所说的那样。

现在,如果您认为操作系统会自动释放非托管代码,那么事实并非如此。如果您的应用程序未正确处理,许多句柄和锁可能仍处于活动状态。

You write your custom code to free objects in your disposable class. It's for you to write code to free unmanaged and managed code.

This is why we need Dispose function. All freeing up of memory is not automatic, like you said in unmanaged code's case.

Now, if you think that unmanaged code is automatically freed by OS, it's not true. There are many handles and locks that may remain active if not properly disposed by your application.

┼── 2024-09-06 08:21:35

首先,我想指出IDisposable不仅仅适用于Windows内核对象或非托管内存。相反,它是针对垃圾收集不理解的事物(其中内核对象是一个特例)。

一个小例子,只是为了给您一个想法:

    sealed class Logger : IDisposable
    {
        private bool _disposed = false;

        public Logger()
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STARTED" );
        }

        ~Logger()
        {
            Dispose();
        }

        public void Dispose()
        {
            if ( !_disposed )
            {
                System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STOPPED" );
                _disposed = true;
            }
        }

        public void WriteMessage( string msg )
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "MESSAGE: " + msg );
        }
    }

请注意,此 Logger 类不“保存”任何内核对象样式资源。它打开文件并立即关闭它。然而,背后的逻辑是,当对象被销毁时,它应该写入“LOG STOPPED”。这个逻辑是 GC 无法理解的——而这就是使用 IDisposable 的地方。

因此,您不能指望 Windows 会在您之后清理内核对象。可能还有一些事情连 Windows 也不知道。

然而,话虽这么说,只要您的一次性对象被正确编写(即在终结器中调用 Dispose),它们仍将在进程退出时由 CLR 处置。

所以,答案是:是的,没有必要在项目退出之前调用 Dispose 。但不是因为可处置资源是内核对象(因为它们不一定是)。


编辑

正如 Josh Einstein 正确指出的那样,终结器实际上不能保证在进程退出时运行。那么答案就变成了:总是调用 Dispose,只是为了被起诉

First, I would like to point out that IDisposable is not only for Windows kernel objects or unmanaged memory. Rather, it is for things that the garbage collection does not understand (of which kernel objects are a special case).

A small example, just to give you an idea:

    sealed class Logger : IDisposable
    {
        private bool _disposed = false;

        public Logger()
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STARTED" );
        }

        ~Logger()
        {
            Dispose();
        }

        public void Dispose()
        {
            if ( !_disposed )
            {
                System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STOPPED" );
                _disposed = true;
            }
        }

        public void WriteMessage( string msg )
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "MESSAGE: " + msg );
        }
    }

Note that this Logger class does not "hold" any kernel-object-style resources. It opens file and closes it right away. However, there is this logic behind that it should write "LOG STOPPED" when the object is destroyed. This logic is something that GC cannot understand - and this is the place to use IDisposable.

Therefore, you cannot count on Windows cleaning up kernel objects after you. There might still be something even Windows doesn't know.

That being said, however, provided your disposable objects are properly written (i.e. calling Dispose in finalizer), they will still be disposed by the CLR upon process exit.

So, the answer is: yes, it is not necessary to call Dispose just before project exit. But NOT because disposable resources are kernel objects (because they're not necessarily are).


Edit

As Josh Einstein correctly pointed out, finalizers are actually not guaranteed to run on process exit. So then the answer becomes: always call Dispose, just to be sue

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文