Skip to content

Commit c00a335

Browse files
author
Oren (electricessence)
committed
Updated for release with both .NET Standard 2.0 and 2.1
1 parent 091f231 commit c00a335

14 files changed

+257
-351
lines changed

AsyncDisposableBase.cs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,69 @@
11
/*!
22
* @author electricessence / https://github.com/electricessence/
3-
* Licensing: MIThttps://github.com/electricessence/Open.Disposable/blob/master/LISCENSE.md
3+
* Licensing: MIT https://github.com/electricessence/Open.Disposable/blob/master/LISCENSE.md
44
*/
55

66
using System.Threading.Tasks;
77

88
namespace Open.Disposable
99
{
10-
public abstract class AsyncDisposableBase
11-
: DisposableStateBase, Open.Disposable.Async.IAsyncDisposable
12-
{
13-
protected abstract ValueTask OnDisposeAsync();
10+
/// <summary>
11+
/// A base class for properly implementing IAsyncDisposable but also allowing for synchronous use of IDispose.
12+
/// Only implementing OnDisposeAsync is enough to properly handle disposal.
13+
/// </summary>
14+
public abstract class AsyncDisposableBase : DisposableBase
15+
#if NETSTANDARD2_1
16+
,System.IAsyncDisposable
17+
#endif
18+
{
19+
/// <summary>
20+
/// Without overriding OnDispose, OnDisposeAsync will be called no matter what depending on how the object is disposed.
21+
/// </summary>
22+
/// <param name="mode">The method by which disposal was triggered.</param>
23+
protected abstract ValueTask OnDisposeAsync(AsyncDisposeMode mode);
1424

15-
/// <inheritdoc />
16-
public ValueTask DisposeAsync()
25+
/// <inheritdoc />
26+
/// <summary>
27+
/// If overridden, expect OnDisposeAsync to be called only when DisposeAsync is called, unless base.OnDispose(calledExplicitly) is implemented.
28+
/// </summary>
29+
protected override void OnDispose(bool calledExplicitly)
30+
{
31+
var dispose = OnDisposeAsync(calledExplicitly ? AsyncDisposeMode.Dispose : AsyncDisposeMode.Finalized);
32+
// Was it purely synchronous?
33+
if(!dispose.IsCompletedSuccessfully)
34+
{
35+
/*
36+
* If the OnDisposeAsync method doesn't complete immediately
37+
* (it could internally defer an action with a task)
38+
* then it's a signal that we must wait.
39+
*/
40+
41+
dispose.AsTask().Wait();
42+
43+
/* This is obviously not ideal, but is a good fallback for this case. */
44+
}
45+
}
46+
47+
/// <inheritdoc />
48+
public ValueTask DisposeAsync()
1749
{
18-
if (!StartDispose())
50+
/*
51+
* Note about the BeforeDispose event:
52+
* Although this is asynchronous, it's not this class' responsibility to decide how subscribers will behave.
53+
* A subscriber should smartly defer responses when possible, or only respond in a properly synchronous non-blockin way.
54+
*/
55+
56+
if (!StartDispose(true))
1957
return new ValueTask();
2058

2159
var dispose = true;
2260
try
2361
{
24-
var d = OnDisposeAsync();
62+
var d = OnDisposeAsync(AsyncDisposeMode.DisposeAsync);
2563
if (!d.IsCompletedSuccessfully)
2664
{
2765
dispose = false;
28-
return OnDisposeAsyncInternal();
66+
return OnDisposeAsyncInternal(d);
2967
}
3068
}
3169
finally
@@ -36,11 +74,11 @@ public ValueTask DisposeAsync()
3674
return new ValueTask();
3775
}
3876

39-
private async ValueTask OnDisposeAsyncInternal()
77+
private async ValueTask OnDisposeAsyncInternal(ValueTask onDispose)
4078
{
4179
try
4280
{
43-
await OnDisposeAsync();
81+
await onDispose;
4482
}
4583
finally
4684
{

AsyncDisposeHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public AsyncDisposeHandler(Func<ValueTask> action)
1818

1919
Func<ValueTask> _action;
2020

21-
protected override ValueTask OnDisposeAsync()
21+
protected override ValueTask OnDisposeAsync(AsyncDisposeMode mode)
2222
=> Interlocked.Exchange(ref _action, null).Invoke();
2323
}
2424
}

AsyncDisposeMode.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*!
2+
* @author electricessence / https://github.com/electricessence/
3+
* Licensing: MIT https://github.com/electricessence/Open.Disposable/blob/master/LISCENSE.md
4+
*/
5+
6+
namespace Open.Disposable
7+
{
8+
public enum AsyncDisposeMode
9+
{
10+
Dispose,
11+
DisposeAsync,
12+
Finalized
13+
}
14+
}

DeferredCleanupBase.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
* Licensing: MIThttps://github.com/electricessence/Open.Disposable/blob/master/LISCENSE.md
44
*/
55

6-
using Open.Diagnostics;
76
using System;
7+
using System.Diagnostics;
88
using System.Threading;
99
using System.Threading.Tasks;
1010

11-
1211
namespace Open.Disposable
1312
{
1413
public abstract class DeferredCleanupBase : DisposableBase
@@ -66,7 +65,7 @@ protected void ResetTimer()
6665

6766
public void SetCleanup(CleanupMode mode = CleanupMode.Deferred)
6867
{
69-
if (IsDisposed)
68+
if (WasDisposed)
7069
return;
7170

7271
switch (mode)
@@ -96,7 +95,7 @@ public void SetCleanup(CleanupMode mode = CleanupMode.Deferred)
9695
case CleanupMode.ImmediateDeferred:
9796
lock (_timerSync)
9897
{
99-
if (!IsDisposed && LastCleanup != DateTime.MaxValue)
98+
if (!WasDisposed && LastCleanup != DateTime.MaxValue)
10099
{
101100
// No past due action in order to prevent another thread from firing...
102101
LastCleanup = DateTime.MaxValue;
@@ -114,10 +113,10 @@ public void SetCleanup(CleanupMode mode = CleanupMode.Deferred)
114113

115114
public void DeferCleanup()
116115
{
117-
if (IsDisposed) return;
116+
if (WasDisposed) return;
118117
lock (_timerSync)
119118
{
120-
if (IsDisposed) return;
119+
if (WasDisposed) return;
121120
IsRunning = true;
122121

123122
if (_cleanupTimer == null)
@@ -141,7 +140,7 @@ public void ClearCleanup()
141140

142141
private void Cleanup(object state = null)
143142
{
144-
if (IsDisposed)
143+
if (WasDisposed)
145144
return; // If another thread enters here after disposal don't allow.
146145

147146
try
@@ -150,10 +149,9 @@ private void Cleanup(object state = null)
150149
}
151150
catch (Exception ex)
152151
{
153-
ex.WriteToDebug();
152+
Debug.WriteLine(ex.ToString());
154153
}
155154

156-
157155
lock (_timerSync)
158156
{
159157
LastCleanup = DateTime.Now;

DisposableBase.cs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
*/
55

66
using System;
7+
using System.Diagnostics;
78

89
namespace Open.Disposable
910
{
10-
11-
/// <summary>
12-
/// A base class for implementing other disposables. Properly implements the (thread-safe) dispose pattern using DisposeHelper.
13-
///
14-
/// Provides useful properties and methods to allow for checking if this instance has already been disposed and provides a "BeforeDispose" event for other classes to react to.
15-
/// </summary>
11+
/// <summary>
12+
/// A base class for properly implementing the synchronous dispose pattern.
13+
/// Implement OnDispose to handle disposal.
14+
/// </summary>
1615
public abstract class DisposableBase : DisposableStateBase, IDisposable
1716
{
1817
/// <inheritdoc />
@@ -27,20 +26,51 @@ public void Dispose()
2726
Dispose(false);
2827
}
2928

29+
/// <summary>
30+
/// When implemented will be called (only once) when being disposed.
31+
/// </summary>
32+
/// <param name="calledExplicitly">True if called through code in the runtime, or false if finalized by the garbage collector.</param>
3033
protected abstract void OnDispose(bool calledExplicitly);
3134

32-
private void Dispose(bool calledExplicitly)
35+
protected bool StartDispose(bool calledExplicitly)
36+
{
37+
try
38+
{
39+
if (StartDispose())
40+
return true;
41+
42+
// Already called? No need to GC suppress.
43+
calledExplicitly = false;
44+
return false;
45+
}
46+
catch (Exception eventBeforeDisposeException) when (!calledExplicitly)
47+
{
48+
if (Debugger.IsAttached)
49+
Debug.Fail(eventBeforeDisposeException.ToString());
50+
51+
return true; // If we even got this far with !calledExplicitly then it must be the first time and we should proceed.
52+
}
53+
finally
54+
{
55+
if (calledExplicitly)
56+
GC.SuppressFinalize(this);
57+
}
58+
}
59+
60+
private void Dispose(bool calledExplicitly)
3361
{
34-
if (!StartDispose())
35-
return;
62+
if (!StartDispose(calledExplicitly))
63+
return;
3664

37-
if(calledExplicitly)
38-
GC.SuppressFinalize(this);
39-
4065
try
4166
{
4267
OnDispose(calledExplicitly);
4368
}
69+
catch (Exception onDisposeException) when (!calledExplicitly)
70+
{
71+
if (Debugger.IsAttached)
72+
Debug.Fail(onDisposeException.ToString());
73+
}
4474
finally
4575
{
4676
Disposed();

0 commit comments

Comments
 (0)