我需要释放SemaphoreSlim吗?

41
根据文档:

"SemaphoreSlim不使用Windows内核信号量"。

是否有特殊资源被SemaphoreSlim使用,使得在不再使用SemaphoreSlim时调用Dispose变得重要?

你可以自己查看此处的源代码来进行验证,但是一个强烈的提示是:它实现了IDisposable接口。 - Steve
5
HttpClient 告诉我们不要盲目地处理所有实现了 IDisposable 接口的对象。 - user247702
4
我不同意那个说法,问题不在于处理 HttpClient,而是如果你每次发送 HTTP 请求都创建一个新实例,这将阻止你充分利用连接重用。 - Matthew
实际问题在于处理httpclient时会出现套接字饥饿的情况。 - Mark Lauter
4个回答

39
如果您访问了 AvailableWaitHandle 属性,那么是的,您必须调用 Dispose() 方法来清理未经处理的资源。
如果您没有访问 AvailableWaitHandle,那么不需要调用 Dispose() 方法来执行任何重要操作。 SemaphoreSlim 将根据需要创建一个 ManualResetEvent,如果您访问 AvailableWaitHandle 属性,则可能会派上用场。例如,如果您需要等待多个句柄。如果您访问了 AvailableWaitHandle 属性,然后未调用 Dispose() 方法,您将拥有一个泄漏的 ManualResetEvent,它包装了对未经处理的 CreateEvent 资源的句柄,需要相应的 CloseHandle 调用以进行清理。
正如其他帖子中指出的那样,当您完成使用实现了 IDisposable 接口的任何对象时,应该调用 Dispose() 方法。在这种情况下,尽管技术上可能是安全的,但忽略此做法存在几个风险:
1. 我的陈述基于对 .NET 4.6.1 的参考源代码。未来版本的框架始终有可能更改 SemaphoreSlim 的行为,使得需要调用 Dispose() 方法。
2. 如果您的 SemaphoreSlim 在类外部暴露,调用代码可能会引用 AvailableWaitHandle 属性,而没有意识到您的类没有处置 SemaphoreSlim,从而创建未经处理的资源泄漏。

17

可以。

它可能使用一个ManualResetEvent,该事件使用SafeHandleSafeWaitHandle,并且它具有非托管句柄。

您可以在此参考源代码中看到它。

SafeHandle是可终结的,因此如果您不释放它(通过释放SemaphoreSlim),它将进入终结器,终结器需要为您执行释放操作。由于终结器是单线程的,因此在某些情况下可能会过度使用,因此始终建议处理可终结对象。


1
只有在调用AvailableWaitHandle时,才会创建SafeHandle。如果您只在不调用AvailableWaitHandle的包装器内使用SemaphoreSlim(如下所建议),则不会出现finalize/dispose问题。 - sjb-sjb
@sjb-sjb 是的,我同意。即使任务是可替换的...但我们从未真正放弃它。 - undefined
有趣的是,出于同样的原因——AsyncWaitHandle。请参阅https://stackoverflow.com/questions/3734280/is-it-considered-acceptable-to-not-call-dispose-on-a-tpl-task-object/3734298#3734298 - undefined

7

对于任何实现了 IDisposable 接口的类,您应该始终调用 Dispose() 方法(或将其放在 using 语句中),而不是基于其内部实现来做出决定。类的作者已经通过实现 IDisposable 接口为您做出了这个决定。


3
确实,这是一般规则。我的问题具体涉及SemaphoreSlim。请注意,SemaphoreSlim的用例通常不倾向于使用“using”。 - Tom Deseyn
9
实现 IDisposable 接口的决定并不总是由类的作者来做。有许多接口(如 IEnumerator<T>)继承了 IDisposable,以允许某些实现在使用后需要进行清理。即使实现 IEnumerable<T> 接口的类的契约指定其特定的 GetEnumerator 实现将始终返回一个可以安全地丢弃的对象,任何由 IEnumerable<T>.GetEnumerator 返回的对象都必须实现 IDisposable 接口,无论它是否需要进行清理。 - supercat

7

对于许多其他类,我同意i3arnon的看法,但对于SemaphoreSlim,我会选择Tim的评论。如果您在低级类中使用SemaphoreSlim并且必须处理它,则实际上程序中的几乎所有内容都将成为IDisposable,而事实上这是不必要的。考虑到AvailableWaitHandle相当专业化且通常不会被使用,这一点更加真实。

为了防止其他编码人员访问AvailableWaitHandle,您可以将其包装在一个非一次性类中。例如,您可以在Cleary和Hanselman的包装器中看到这一点,两者都基于Stephen Toub的帖子(顺便说一句,该帖子没有Dispose)。

P.S. 关于IDisposable协定,应在文档中指定只有在访问AvailableWaitHandle时才需要Dispose。


图书馆客户端代码不应依赖于低级细节,比如“为什么需要处理”和“在不引起问题的情况下可以避免处理”。如果一个对象实现了IDisposable接口,客户端有责任控制实例的生命周期,并在生命周期结束时调用IDisposable.DisposeIAsyncDisposable.DisposeAsync。实现细节可能随着库的下一个版本而发生变化。今天不处理一个实例可能会被原谅,但明天它将破坏你的代码并产生意想不到的副作用。 - user21970328

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接