Skip to content

Commit 3bc63e0

Browse files
committed
Add AsyncDisposable
1 parent b5b6af5 commit 3bc63e0

File tree

8 files changed

+232
-43
lines changed

8 files changed

+232
-43
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER
2+
using System.Threading.Tasks;
3+
4+
using NUnit.Framework;
5+
6+
namespace CodeJam
7+
{
8+
public class AsyncDisposableTest
9+
{
10+
[Test]
11+
public async Task TestDisposable()
12+
{
13+
var value = 0;
14+
var disposable = AsyncDisposable.Create(() =>
15+
{
16+
value++;
17+
return default;
18+
});
19+
// Fails if Disposable.Create returns struct
20+
var disposable2 = disposable;
21+
22+
Assert.That(value, Is.EqualTo(0));
23+
24+
disposable.Dispose();
25+
Assert.That(value, Is.EqualTo(1));
26+
27+
disposable.Dispose();
28+
Assert.That(value, Is.EqualTo(1));
29+
30+
disposable2.Dispose();
31+
Assert.That(value, Is.EqualTo(1));
32+
33+
await disposable.DisposeAsync();
34+
Assert.That(value, Is.EqualTo(1));
35+
}
36+
}
37+
}
38+
#endif

CodeJam.Main.Tests/CodeJam.Main.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<RootNamespace>CodeJam</RootNamespace>
99
<ProjectGuid>{DD65E3F2-9658-4242-A8AA-056028473CB1}</ProjectGuid>
1010
<OutputType>Library</OutputType>
11-
<TargetFrameworks>net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp3.0;netcoreapp2.1;netcoreapp1.1</TargetFrameworks>
11+
<TargetFrameworks>netcoreapp3.0;net48;net472;net471;net47;net461;net45;net40;net35;net20;netcoreapp2.1;netcoreapp1.1</TargetFrameworks>
1212
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1313
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
1414
</PropertyGroup>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Threading;
3+
4+
using JetBrains.Annotations;
5+
6+
namespace CodeJam
7+
{
8+
/// <summary>
9+
/// The <see cref="IDisposable"/> implementation that calls supplied action on <see cref="Dispose"/>.
10+
/// </summary>
11+
/// DONTTOUCH: DO NOT make it a struct, passing the structure by value will result in multiple Dispose() calls.
12+
/// SEEALSO: https://blogs.msdn.microsoft.com/ericlippert/2011/03/14/to-box-or-not-to-box-that-is-the-question/
13+
public class AnonymousDisposable : IDisposable
14+
{
15+
/// <summary>
16+
/// If <c>0</c> - instance not disposed.
17+
/// </summary>
18+
/// <remarks>
19+
/// Use int instead of bool, because <see cref="Interlocked.Exchange(ref double,double)"/> support it.
20+
/// </remarks>
21+
protected int Disposed;
22+
23+
[CanBeNull]
24+
private readonly Action _disposeAction;
25+
26+
/// <summary>Initialize instance.</summary>
27+
/// <param name="disposeAction">The dispose action.</param>
28+
public AnonymousDisposable([CanBeNull] Action disposeAction) => _disposeAction = disposeAction;
29+
30+
/// <inheritdoc />
31+
public void Dispose()
32+
{
33+
if (_disposeAction == null)
34+
return;
35+
var disposed = Interlocked.Exchange(ref Disposed, 1);
36+
if (disposed == 0)
37+
{
38+
try
39+
{
40+
_disposeAction();
41+
}
42+
catch when (OnException(disposed))
43+
{
44+
}
45+
}
46+
}
47+
48+
/// <summary>
49+
/// Restore <see cref="Disposed"/> value and returns <c>false</c>.
50+
/// </summary>
51+
protected bool OnException(int disposed)
52+
{
53+
Interlocked.Exchange(ref Disposed, disposed);
54+
return false;
55+
}
56+
}
57+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER
2+
using System;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
using JetBrains.Annotations;
7+
8+
namespace CodeJam
9+
{
10+
/// <summary>
11+
/// <see cref="IDisposable"/> and <see cref="IAsyncDisposable"/> implementation.
12+
/// </summary>
13+
[PublicAPI]
14+
public class AsyncAnonymousDisposable : AnonymousDisposable, IAsyncDisposable
15+
{
16+
[CanBeNull]
17+
private Func<ValueTask> _asyncDisposeAction;
18+
19+
/// <summary>
20+
/// Initialize instance.
21+
/// </summary>
22+
/// <param name="syncDisposeAction">Sync dispose action.</param>
23+
/// <param name="asyncDisposeAction">Async dispose action.</param>
24+
public AsyncAnonymousDisposable([CanBeNull] Action syncDisposeAction, [CanBeNull] Func<ValueTask> asyncDisposeAction)
25+
: base(syncDisposeAction) =>
26+
_asyncDisposeAction = asyncDisposeAction;
27+
28+
/// <inheritdoc/>
29+
public async ValueTask DisposeAsync()
30+
{
31+
if (_asyncDisposeAction == null)
32+
return;
33+
var disposed = Interlocked.Exchange(ref Disposed, 1);
34+
if (disposed == 0)
35+
try
36+
{
37+
await _asyncDisposeAction();
38+
}
39+
catch when (OnException(disposed)) { }
40+
}
41+
}
42+
}
43+
#endif

CodeJam.Main/AsyncDisposable.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#if NETCOREAPP30_OR_GREATER || NETSTANDARD21_OR_GREATER
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Threading.Tasks;
5+
6+
using JetBrains.Annotations;
7+
8+
namespace CodeJam
9+
{
10+
/// <summary>
11+
/// Helper methods for <see cref="IAsyncDisposable"/>.
12+
/// </summary>
13+
[PublicAPI]
14+
public static class AsyncDisposable
15+
{
16+
/// <summary>
17+
/// <see cref="IDisposable"/> and <see cref="IAsyncDisposable"/> implementation without any action.
18+
/// </summary>
19+
public static readonly AsyncAnonymousDisposable Empty =
20+
new AsyncAnonymousDisposable(null, null);
21+
22+
/// <summary>
23+
/// Creates anonymous disposable with sync and async dispose actions.
24+
/// </summary>
25+
public static AsyncAnonymousDisposable Create(Action syncAction, Func<ValueTask> asyncAction) =>
26+
new AsyncAnonymousDisposable(syncAction, asyncAction);
27+
28+
/// <summary>
29+
/// Creates anonymous async disposable.
30+
/// </summary>
31+
public static AsyncAnonymousDisposable Create(Func<ValueTask> asyncAction) =>
32+
new AsyncAnonymousDisposable(
33+
() => asyncAction?.Invoke().GetAwaiter().GetResult(),
34+
asyncAction);
35+
36+
// <summary>Combine multiple <see cref="IAsyncDisposable"/> instances into single one.</summary>
37+
/// <param name="disposables">The disposables.</param>
38+
/// <returns>Instance of <see cref="AsyncAnonymousDisposable"/> that will dispose the specified disposables.</returns>
39+
public static AsyncAnonymousDisposable Merge([NotNull, ItemNotNull] this IEnumerable<IAsyncDisposable> disposables) =>
40+
Create(disposables.DisposeAllAsync);
41+
42+
// <summary>Combine multiple <see cref="IAsyncDisposable"/> instances into single one.</summary>
43+
/// <param name="disposables">The disposables.</param>
44+
/// <returns>Instance of <see cref="AsyncAnonymousDisposable"/> that will dispose the specified disposables.</returns>
45+
public static AsyncAnonymousDisposable Merge([NotNull] params IAsyncDisposable[] disposables) =>
46+
Merge((IEnumerable<IAsyncDisposable>)disposables);
47+
}
48+
}
49+
#endif

CodeJam.Main/CodeJam.Main.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<RootNamespace>CodeJam</RootNamespace>
1010
<ProjectGuid>{2F2046CC-FB47-4318-B335-5A82B04B6C40}</ProjectGuid>
1111
<OutputType>Library</OutputType>
12-
<TargetFrameworks>net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp3.0;netcoreapp2.0;netcoreapp1.0</TargetFrameworks>
12+
<TargetFrameworks>netcoreapp3.0;net472;net461;net45;net40;net35;net20;netstandard2.1;netstandard2.0;netstandard1.5;netstandard1.3;netcoreapp2.0;netcoreapp1.0</TargetFrameworks>
1313
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1414
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
1515
<LangVersion>8.0</LangVersion>

CodeJam.Main/Disposable.cs

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Threading;
4+
using System.Threading.Tasks;
45

56
using JetBrains.Annotations;
67

@@ -22,44 +23,6 @@ public sealed class EmptyDisposable : IDisposable
2223
public void Dispose() { }
2324
}
2425

25-
/// <summary>
26-
/// The <see cref="IDisposable"/> implementation that calls supplied action on <see cref="Dispose"/>.
27-
/// </summary>
28-
/// DONTTOUCH: DO NOT make it a struct, passing the structure by value will result in multiple Dispose() calls.
29-
/// SEEALSO: https://blogs.msdn.microsoft.com/ericlippert/2011/03/14/to-box-or-not-to-box-that-is-the-question/
30-
private sealed class AnonymousDisposable : IDisposable
31-
{
32-
private Action _disposeAction;
33-
34-
/// <summary>Initialize instance.</summary>
35-
/// <param name="disposeAction">The dispose action.</param>
36-
public AnonymousDisposable(Action disposeAction) => _disposeAction = disposeAction;
37-
38-
/// <summary>
39-
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
40-
/// </summary>
41-
public void Dispose()
42-
{
43-
var disposeAction = Interlocked.Exchange(ref _disposeAction, null);
44-
if (disposeAction != null)
45-
{
46-
try
47-
{
48-
disposeAction.Invoke();
49-
}
50-
catch when (OnException(disposeAction))
51-
{
52-
}
53-
}
54-
}
55-
56-
private bool OnException(Action disposeAction)
57-
{
58-
Interlocked.Exchange(ref _disposeAction, disposeAction);
59-
return false;
60-
}
61-
}
62-
6326
/// <summary>
6427
/// The <see cref="IDisposable"/> implementation that calls supplied action on <see cref="Dispose"/>.
6528
/// </summary>

CodeJam.Main/DisposableExtensions.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ public static void DisposeAll([NotNull, ItemNotNull, InstantHandle] this IEnumer
2929
}
3030
catch (Exception ex)
3131
{
32-
if (exceptions == null)
33-
exceptions = new List<Exception>();
34-
32+
exceptions ??= new List<Exception>();
3533
exceptions.Add(ex);
3634
}
3735
}
@@ -74,6 +72,47 @@ public static ValueTask DisposeAsync([NotNull] this IDisposable disposable)
7472
disposable.Dispose();
7573
return new ValueTask();
7674
}
75+
76+
/// <summary>Invokes the dispose for each item in the <paramref name="disposables"/>.</summary>
77+
/// <param name="disposables">The multiple <see cref="IDisposable"/> instances.</param>
78+
/// <exception cref="AggregateException"></exception>
79+
public static async ValueTask DisposeAllAsync(
80+
[NotNull, ItemNotNull, InstantHandle] this IEnumerable<IAsyncDisposable> disposables)
81+
{
82+
List<Exception> exceptions = null;
83+
84+
foreach (var item in disposables)
85+
try
86+
{
87+
await item.DisposeAsync();
88+
}
89+
catch (Exception ex)
90+
{
91+
exceptions ??= new List<Exception>();
92+
exceptions.Add(ex);
93+
}
94+
95+
if (exceptions != null)
96+
throw new AggregateException(exceptions);
97+
}
98+
99+
/// <summary>Invokes the dispose for each item in the <paramref name="disposables"/>.</summary>
100+
/// <param name="disposables">The multiple <see cref="IDisposable"/> instances.</param>
101+
/// <param name="exceptionHandler">The exception handler.</param>
102+
public static async ValueTask DisposeAllAsync(
103+
[NotNull, ItemNotNull, InstantHandle] this IEnumerable<IAsyncDisposable> disposables,
104+
[NotNull, InstantHandle] Func<Exception, bool> exceptionHandler)
105+
{
106+
foreach (var item in disposables)
107+
try
108+
{
109+
await item.DisposeAsync();
110+
}
111+
catch (Exception ex) when (exceptionHandler(ex))
112+
{
113+
ex.LogToCodeTraceSourceOnCatch(true);
114+
}
115+
}
77116
#endif
78117
}
79118
}

0 commit comments

Comments
 (0)