Skip to content

Commit 1cd9a20

Browse files
committed
Add IAsyncConnection interface with async StateChanged event
1 parent 58b241e commit 1cd9a20

File tree

6 files changed

+174
-59
lines changed

6 files changed

+174
-59
lines changed

Net.SocketIO/ConnectionStateTracker.cs

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
namespace Ecng.Net;
22

33
/// <summary>
4-
/// Tracks the connection states of multiple IConnection instances and aggregates their overall state.
4+
/// Tracks the connection states of multiple <see cref="IAsyncConnection"/> instances and aggregates their overall state.
55
/// </summary>
6-
public class ConnectionStateTracker : Disposable, IConnection
6+
public class ConnectionStateTracker : Disposable, IAsyncConnection,
7+
#pragma warning disable CS0618 // Type or member is obsolete
8+
IConnection
9+
#pragma warning restore CS0618
710
{
811
/// <summary>
9-
/// Wraps an IConnection instance to listen for its state changes.
12+
/// Wraps an <see cref="IAsyncConnection"/> instance to listen for its state changes.
1013
/// </summary>
1114
private class ConnectionWrapper : Disposable
1215
{
13-
private readonly IConnection _connection;
16+
private readonly IAsyncConnection _connection;
1417
private readonly Action _stateChanged;
1518

1619
/// <summary>
@@ -24,7 +27,7 @@ private class ConnectionWrapper : Disposable
2427
/// <param name="connection">The connection to wrap.</param>
2528
/// <param name="stateChanged">Callback invoked when the connection state changes.</param>
2629
/// <exception cref="ArgumentNullException">Thrown when connection or stateChanged is null.</exception>
27-
public ConnectionWrapper(IConnection connection, Action stateChanged)
30+
public ConnectionWrapper(IAsyncConnection connection, Action stateChanged)
2831
{
2932
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
3033
_stateChanged = stateChanged ?? throw new ArgumentNullException(nameof(stateChanged));
@@ -46,22 +49,56 @@ protected override void DisposeManaged()
4649
/// <summary>
4750
/// Handles the state change event from the wrapped connection.
4851
/// </summary>
49-
/// <param name="newState">The new state of the connection.</param>
50-
private void OnStateChanged(ConnectionStates newState)
52+
private ValueTask OnStateChanged(ConnectionStates newState, CancellationToken cancellationToken)
5153
{
5254
State = newState;
5355
_stateChanged();
56+
return default;
5457
}
5558
}
5659

57-
private readonly CachedSynchronizedDictionary<IConnection, ConnectionWrapper> _connections = [];
60+
#pragma warning disable CS0618 // Type or member is obsolete
61+
/// <summary>
62+
/// Adapts an <see cref="IConnection"/> to <see cref="IAsyncConnection"/>.
63+
/// </summary>
64+
private sealed class ConnectionAdapter(IConnection connection) : IAsyncConnection
65+
{
66+
public event Func<ConnectionStates, CancellationToken, ValueTask> StateChanged
67+
{
68+
add => connection.StateChanged += state => value(state, default);
69+
remove { }
70+
}
71+
72+
public ValueTask ConnectAsync(CancellationToken cancellationToken)
73+
=> connection.ConnectAsync(cancellationToken);
74+
75+
public void Disconnect()
76+
=> connection.Disconnect();
77+
}
78+
#pragma warning restore CS0618
79+
80+
private readonly CachedSynchronizedDictionary<IAsyncConnection, ConnectionWrapper> _connections = [];
5881
private readonly Lock _currStateLock = new();
5982
private ConnectionStates _currState = ConnectionStates.Disconnected;
6083

61-
/// <summary>
62-
/// Occurs when the overall connection state changes.
63-
/// </summary>
64-
public event Action<ConnectionStates> StateChanged;
84+
private event Func<ConnectionStates, CancellationToken, ValueTask> _asyncStateChanged;
85+
#pragma warning disable CS0618 // Type or member is obsolete
86+
private event Action<ConnectionStates> _syncStateChanged;
87+
88+
/// <inheritdoc />
89+
event Action<ConnectionStates> IConnection.StateChanged
90+
{
91+
add => _syncStateChanged += value;
92+
remove => _syncStateChanged -= value;
93+
}
94+
#pragma warning restore CS0618
95+
96+
/// <inheritdoc />
97+
event Func<ConnectionStates, CancellationToken, ValueTask> IAsyncConnection.StateChanged
98+
{
99+
add => _asyncStateChanged += value;
100+
remove => _asyncStateChanged -= value;
101+
}
65102

66103
/// <summary>
67104
/// Connects all tracked connections asynchronously.
@@ -89,15 +126,25 @@ public void Disconnect()
89126
/// Adds a connection to be tracked.
90127
/// </summary>
91128
/// <param name="connection">The connection to add.</param>
92-
public void Add(IConnection connection)
129+
public void Add(IAsyncConnection connection)
93130
=> _connections.Add(connection, new(connection, UpdateOverallState));
94131

132+
/// <summary>
133+
/// Adds a connection to be tracked.
134+
/// </summary>
135+
/// <param name="connection">The connection to add.</param>
136+
[Obsolete("Use Add(IAsyncConnection) instead.")]
137+
#pragma warning disable CS0618 // Type or member is obsolete
138+
public void Add(IConnection connection)
139+
#pragma warning restore CS0618
140+
=> Add(new ConnectionAdapter(connection));
141+
95142
/// <summary>
96143
/// Removes a tracked connection.
97144
/// </summary>
98145
/// <param name="connection">The connection to remove.</param>
99146
/// <returns>True if the connection was successfully removed; otherwise, false.</returns>
100-
public bool Remove(IConnection connection)
147+
public bool Remove(IAsyncConnection connection)
101148
{
102149
if (!_connections.TryGetAndRemove(connection, out var wrapper))
103150
return false;
@@ -107,7 +154,24 @@ public bool Remove(IConnection connection)
107154
return true;
108155
}
109156

110-
private IConnection[] Connections => _connections.CachedKeys;
157+
/// <summary>
158+
/// Removes a tracked connection.
159+
/// </summary>
160+
/// <param name="connection">The connection to remove.</param>
161+
/// <returns>True if the connection was successfully removed; otherwise, false.</returns>
162+
[Obsolete("Use Remove(IAsyncConnection) instead.")]
163+
#pragma warning disable CS0618 // Type or member is obsolete
164+
public bool Remove(IConnection connection)
165+
#pragma warning restore CS0618
166+
{
167+
var key = _connections.CachedPairs.FirstOrDefault(p => p.Key is ConnectionAdapter).Key;
168+
if (key is null)
169+
return false;
170+
171+
return Remove(key);
172+
}
173+
174+
private IAsyncConnection[] Connections => _connections.CachedKeys;
111175
private ConnectionWrapper[] Wrappers => _connections.CachedValues;
112176

113177
/// <summary>
@@ -161,6 +225,6 @@ private void UpdateOverallState()
161225
_currState = newState;
162226
}
163227

164-
StateChanged?.Invoke(newState);
228+
_syncStateChanged?.Invoke(newState);
165229
}
166230
}

Net.SocketIO/IAsyncConnection.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace Ecng.Net;
2+
3+
/// <summary>
4+
/// Defines an asynchronous contract for a connection that can be established and disconnected.
5+
/// </summary>
6+
public interface IAsyncConnection
7+
{
8+
/// <summary>
9+
/// Occurs when the connection state has changed.
10+
/// </summary>
11+
event Func<ConnectionStates, CancellationToken, ValueTask> StateChanged;
12+
13+
/// <summary>
14+
/// Asynchronously connects to a target.
15+
/// </summary>
16+
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
17+
/// <returns>A task representing the asynchronous operation.</returns>
18+
ValueTask ConnectAsync(CancellationToken cancellationToken);
19+
20+
/// <summary>
21+
/// Disconnects the current connection.
22+
/// </summary>
23+
void Disconnect();
24+
}

Net.SocketIO/IConnection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace Ecng.Net;
33
/// <summary>
44
/// Defines a standard contract for a connection that can be established and disconnected.
55
/// </summary>
6+
[Obsolete("Use IAsyncConnection instead.")]
67
public interface IConnection
78
{
89
/// <summary>

Net.SocketIO/WebSocketClient.cs

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,17 @@
1010
/// <summary>
1111
/// Represents a client for WebSocket connections.
1212
/// </summary>
13-
public class WebSocketClient : Disposable, IConnection
13+
public class WebSocketClient : Disposable, IAsyncConnection,
14+
#pragma warning disable CS0618 // Type or member is obsolete
15+
IConnection
16+
#pragma warning restore CS0618
1417
{
1518
private ClientWebSocket _ws;
1619
private CancellationTokenSource _source;
1720

1821
private readonly SynchronizedDictionary<CancellationTokenSource, bool> _disconnectionStates = [];
1922

2023
private readonly Func<Exception, CancellationToken, ValueTask> _error;
21-
private readonly Func<ConnectionStates, CancellationToken, ValueTask> _stateChanged;
2224
private readonly Func<WebSocketClient, WebSocketMessage, CancellationToken, ValueTask> _process;
2325
private readonly Action<string, object> _infoLog;
2426
private readonly Action<string, object> _errorLog;
@@ -38,6 +40,7 @@ public class WebSocketClient : Disposable, IConnection
3840
/// <param name="errorLog">Action to log error messages.</param>
3941
/// <param name="verboseLog">Action to log verbose messages.</param>
4042
/// <exception cref="ArgumentNullException">If any required parameter is null.</exception>
43+
[Obsolete("Use constructor with Func<T, CancellationToken, ValueTask> callbacks instead.")]
4144
public WebSocketClient(string url, Action<ConnectionStates> stateChanged, Action<Exception> error,
4245
Func<WebSocketMessage, CancellationToken, ValueTask> process,
4346
Action<string, object> infoLog, Action<string, object> errorLog, Action<string, object> verboseLog)
@@ -58,6 +61,7 @@ public WebSocketClient(string url, Action<ConnectionStates> stateChanged, Action
5861
/// <param name="errorLog">Action to log error messages.</param>
5962
/// <param name="verboseLog">Action to log verbose messages.</param>
6063
/// <exception cref="ArgumentNullException">If any required parameter is null.</exception>
64+
[Obsolete("Use constructor with Func<T, CancellationToken, ValueTask> callbacks instead.")]
6165
public WebSocketClient(string url, Action<ConnectionStates> stateChanged, Action<Exception> error,
6266
Func<WebSocketClient, WebSocketMessage, CancellationToken, ValueTask> process,
6367
Action<string, object> infoLog, Action<string, object> errorLog, Action<string, object> verboseLog)
@@ -180,10 +184,25 @@ public int ReconnectAttempts
180184
}
181185
}
182186

183-
/// <summary>
184-
/// Occurs when the connection state changes.
185-
/// </summary>
186-
public event Action<ConnectionStates> StateChanged;
187+
#pragma warning disable CS0618 // Type or member is obsolete
188+
private event Action<ConnectionStates> _syncStateChanged;
189+
190+
/// <inheritdoc />
191+
event Action<ConnectionStates> IConnection.StateChanged
192+
{
193+
add => _syncStateChanged += value;
194+
remove => _syncStateChanged -= value;
195+
}
196+
#pragma warning restore CS0618
197+
198+
private event Func<ConnectionStates, CancellationToken, ValueTask> _stateChanged;
199+
200+
/// <inheritdoc />
201+
event Func<ConnectionStates, CancellationToken, ValueTask> IAsyncConnection.StateChanged
202+
{
203+
add => _stateChanged += value;
204+
remove => _stateChanged -= value;
205+
}
187206

188207
/// <summary>
189208
/// Occurs when the client web socket is initialized.
@@ -235,16 +254,18 @@ public async ValueTask ConnectAsync(CancellationToken cancellationToken)
235254
await ConnectImpl(source, false, 0, linked.Token);
236255
}
237256

238-
private ValueTask RaiseStateChangedAsync(ConnectionStates state, CancellationToken cancellationToken)
257+
private async ValueTask RaiseStateChangedAsync(ConnectionStates state, CancellationToken cancellationToken)
239258
{
240259
if (state == ConnectionStates.Failed)
241260
_errorLog("{0}", state);
242261
else
243262
_infoLog("{0}", state);
244263

245264
State = state;
246-
StateChanged?.Invoke(state);
247-
return _stateChanged(state, cancellationToken);
265+
_syncStateChanged?.Invoke(state);
266+
267+
if (_stateChanged is { } handler)
268+
await handler(state, cancellationToken);
248269
}
249270

250271
private ValueTask RaiseErrorAsync(Exception ex, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)