Skip to content

Commit 329f244

Browse files
CopilotIEvangelist
andcommitted
Update Dispose pattern examples to use thread-safe implementation
Co-authored-by: IEvangelist <[email protected]>
1 parent 2888980 commit 329f244

File tree

7 files changed

+66
-47
lines changed

7 files changed

+66
-47
lines changed

docs/standard/garbage-collection/implementing-dispose.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ All non-sealed classes (or Visual Basic classes not modified as `NotInheritable`
9191
9292
### Base class with managed resources
9393

94-
Here's a general example of implementing the dispose pattern for a base class that only owns managed resources.
94+
Here's a general example of implementing the dispose pattern for a base class that only owns managed resources. The example uses <xref:System.Threading.Interlocked.CompareExchange%2A?displayProperty=nameWithType> to ensure thread-safe disposal.
9595

9696
:::code language="csharp" source="../../../samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs":::
9797
:::code language="vb" source="../../../samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb":::
@@ -153,7 +153,7 @@ The following code demonstrates how to handle unmanaged resources by implementin
153153
> The behavior of the `DisposableBaseWithSafeHandle` class is equivalent to the behavior of the [`DisposableBaseWithFinalizer` class in a previous example](#base-class-with-unmanaged-resources), however the approach demonstrated here is safer:
154154
>
155155
> - There is no need to implement a finalizer, because <xref:System.Runtime.InteropServices.SafeHandle> will take care of finalization.
156-
> - There is no need for synchronization to guarantee thread safety. Even though there is a race condition in the `Dispose` implementation of `DisposableBaseWithSafeHandle`, <xref:System.Runtime.InteropServices.SafeHandle> guarantees that <xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType> will be called only once.
156+
> - <xref:System.Runtime.InteropServices.SafeHandle> guarantees that <xref:System.Runtime.InteropServices.SafeHandle.ReleaseHandle%2A?displayProperty=nameWithType> will be called only once, even in multi-threaded scenarios.
157157
158158
### Built-in safe handles in .NET
159159

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
using System;
2+
using System.Threading;
23

34
public class Disposable : IDisposable
45
{
5-
private bool _disposed;
6+
// Detect redundant Dispose() calls in a thread-safe manner.
7+
// _disposed == 0 means Dispose(bool) has not been called yet.
8+
// _disposed == 1 means Dispose(bool) has been already called.
9+
private int _disposed;
10+
611
// <SnippetDispose>
712
public void Dispose()
813
{
@@ -16,21 +21,19 @@ public void Dispose()
1621
// <SnippetDisposeBool>
1722
protected virtual void Dispose(bool disposing)
1823
{
19-
if (_disposed)
24+
// In case _disposed is 0, atomically set it to 1.
25+
// Enter the branch only if the original value is 0.
26+
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
2027
{
21-
return;
22-
}
28+
if (disposing)
29+
{
30+
// Dispose managed state (managed objects).
31+
// ...
32+
}
2333

24-
if (disposing)
25-
{
26-
// Dispose managed state (managed objects).
34+
// Free unmanaged resources.
2735
// ...
2836
}
29-
30-
// Free unmanaged resources.
31-
// ...
32-
33-
_disposed = true;
3437
}
3538
// </SnippetDisposeBool>
3639
}

docs/standard/garbage-collection/snippets/dispose-pattern/vb/Disposable.vb

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
Public Class Disposable : Implements IDisposable
1+
Imports System.Threading
2+
3+
Public Class Disposable : Implements IDisposable
4+
5+
' Detect redundant Dispose() calls in a thread-safe manner.
6+
' _disposed = 0 means Dispose(bool) has not been called yet.
7+
' _disposed = 1 means Dispose(bool) has been already called.
8+
Private _disposed As Integer
29

3-
Dim disposed As Boolean
410
' <SnippetDispose>
511
Public Sub Dispose() _
612
Implements IDisposable.Dispose
@@ -13,19 +19,17 @@
1319

1420
' <SnippetDisposeBool>
1521
Protected Overridable Sub Dispose(disposing As Boolean)
16-
If disposed Then Exit Sub
17-
18-
If disposing Then
19-
20-
' Free managed resources.
22+
' In case _disposed is 0, atomically set it to 1.
23+
' Enter the branch only if the original value is 0.
24+
If Interlocked.CompareExchange(_disposed, 1, 0) = 0 Then
25+
If disposing Then
26+
' Free managed resources.
27+
' ...
28+
End If
29+
30+
' Free unmanaged resources.
2131
' ...
22-
2332
End If
24-
25-
' Free unmanaged resources.
26-
' ...
27-
28-
disposed = True
2933
End Sub
3034
' </SnippetDisposeBool>
3135

samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/base1.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using System;
22
using System.IO;
3+
using System.Threading;
34

45
public class DisposableBase : IDisposable
56
{
6-
// Detect redundant Dispose() calls.
7-
private bool _isDisposed;
7+
// Detect redundant Dispose() calls in a thread-safe manner.
8+
// _isDisposed == 0 means Dispose(bool) has not been called yet.
9+
// _isDisposed == 1 means Dispose(bool) has been already called.
10+
private int _isDisposed;
811

912
// Instantiate a disposable object owned by this class.
1013
private Stream? _managedResource = new MemoryStream();
@@ -19,10 +22,10 @@ public void Dispose()
1922
// Protected implementation of Dispose pattern.
2023
protected virtual void Dispose(bool disposing)
2124
{
22-
if (!_isDisposed)
25+
// In case _isDisposed is 0, atomically set it to 1.
26+
// Enter the branch only if the original value is 0.
27+
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
2328
{
24-
_isDisposed = true;
25-
2629
if (disposing)
2730
{
2831
// Dispose managed state.

samples/snippets/csharp/VS_Snippets_CLR_System/system.idisposable/cs/safe.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Runtime.InteropServices;
4+
using System.Threading;
45
using Microsoft.Win32.SafeHandles;
56

67
// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
@@ -27,8 +28,10 @@ public static LocalAllocHandle Allocate(int numberOfBytes)
2728

2829
public class DisposableBaseWithSafeHandle : IDisposable
2930
{
30-
// Detect redundant Dispose() calls.
31-
private bool _isDisposed;
31+
// Detect redundant Dispose() calls in a thread-safe manner.
32+
// _isDisposed == 0 means Dispose(bool) has not been called yet.
33+
// _isDisposed == 1 means Dispose(bool) has been already called.
34+
private int _isDisposed;
3235

3336
// Managed disposable objects owned by this class
3437
private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10);
@@ -44,10 +47,10 @@ public void Dispose()
4447
// Protected implementation of Dispose pattern.
4548
protected virtual void Dispose(bool disposing)
4649
{
47-
if (!_isDisposed)
50+
// In case _isDisposed is 0, atomically set it to 1.
51+
// Enter the branch only if the original value is 0.
52+
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
4853
{
49-
_isDisposed = true;
50-
5154
if (disposing)
5255
{
5356
// Dispose managed state.

samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/base1.vb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
Imports System.IO
2+
Imports System.Threading
23

34
Public Class DisposableBase
45
Implements IDisposable
56

6-
' Detect redundant Dispose() calls.
7-
Private _isDisposed As Boolean
7+
' Detect redundant Dispose() calls in a thread-safe manner.
8+
' _isDisposed = 0 means Dispose(bool) has not been called yet.
9+
' _isDisposed = 1 means Dispose(bool) has been already called.
10+
Private _isDisposed As Integer
811

912
' Instantiate a disposable object owned by this class.
1013
Private _managedResource As Stream = New MemoryStream()
@@ -17,9 +20,9 @@ Public Class DisposableBase
1720

1821
' Protected implementation of Dispose pattern.
1922
Protected Overridable Sub Dispose(disposing As Boolean)
20-
If Not _isDisposed Then
21-
_isDisposed = True
22-
23+
' In case _isDisposed is 0, atomically set it to 1.
24+
' Enter the branch only if the original value is 0.
25+
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then
2326
If disposing Then
2427
' Dispose managed state.
2528
_managedResource?.Dispose()

samples/snippets/visualbasic/VS_Snippets_CLR_System/system.idisposable/vb/safe.vb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
Imports System
22
Imports System.IO
33
Imports System.Runtime.InteropServices
4+
Imports System.Threading
45
Imports Microsoft.Win32.SafeHandles
56

67
' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
@@ -29,8 +30,10 @@ End Class
2930
Public Class DisposableBaseWithSafeHandle
3031
Implements IDisposable
3132

32-
' Detect redundant Dispose() calls.
33-
Private _isDisposed As Boolean
33+
' Detect redundant Dispose() calls in a thread-safe manner.
34+
' _isDisposed = 0 means Dispose(bool) has not been called yet.
35+
' _isDisposed = 1 means Dispose(bool) has been already called.
36+
Private _isDisposed As Integer
3437

3538
' Managed disposable objects owned by this class
3639
Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10)
@@ -44,9 +47,9 @@ Public Class DisposableBaseWithSafeHandle
4447

4548
' Protected implementation of Dispose pattern.
4649
Protected Overridable Sub Dispose(disposing As Boolean)
47-
If Not _isDisposed Then
48-
_isDisposed = True
49-
50+
' In case _isDisposed is 0, atomically set it to 1.
51+
' Enter the branch only if the original value is 0.
52+
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then
5053
If disposing Then
5154
' Dispose managed state.
5255
_otherUnmanagedResource?.Dispose()

0 commit comments

Comments
 (0)