@@ -109,6 +109,8 @@ class ReachabilityListener : IDisposable
109109 const int ConnectionStatusChangeDelayMs = 100 ;
110110
111111 NWPathMonitor pathMonitor ;
112+ CancellationTokenSource cts = new CancellationTokenSource ( ) ;
113+ int pendingCallbacks ;
112114
113115 internal ReachabilityListener ( )
114116 {
@@ -130,6 +132,10 @@ internal ReachabilityListener()
130132
131133 internal void Dispose ( )
132134 {
135+ cts ? . Cancel ( ) ;
136+ cts ? . Dispose ( ) ;
137+ cts = null ;
138+
133139 if ( pathMonitor != null )
134140 {
135141 pathMonitor . SnapshotHandler = null ;
@@ -170,25 +176,45 @@ void OnRestrictedStateChanged(CTCellularDataRestrictedState state)
170176
171177 async void OnChange ( NetworkReachabilityFlags flags )
172178 {
173- // This function waits up to 1 second, checking the device’s network status every 100 milliseconds.
179+ // Deduplicate: both watchers may fire for the same network change.
180+ // Only the first callback runs the polling loop; subsequent ones are no-ops.
181+ if ( Interlocked . Increment ( ref pendingCallbacks ) > 1 )
182+ return ;
183+
184+ var token = cts ? . Token ?? default ;
185+ if ( token . IsCancellationRequested )
186+ return ;
187+
188+ // This function waits up to 1 second, checking the device's network status every 100 milliseconds.
174189 // If the network status changes, it immediately triggers the ReachabilityChanged event.
175190 var initialAccess = Connectivity . NetworkAccess ;
176191 const int pollingIntervalMs = 100 ;
177192 const int maxWaitTimeMs = 1000 ;
178193 int elapsedTime = 0 ;
179194
180- while ( elapsedTime < maxWaitTimeMs )
195+ try
181196 {
182- await Task . Delay ( pollingIntervalMs ) ;
183- elapsedTime += pollingIntervalMs ;
184- var currentAccess = Connectivity . NetworkAccess ;
185- if ( currentAccess != initialAccess )
197+ while ( elapsedTime < maxWaitTimeMs )
186198 {
187- ReachabilityChanged ? . Invoke ( ) ;
188- return ;
199+ await Task . Delay ( pollingIntervalMs , token ) ;
200+ elapsedTime += pollingIntervalMs ;
201+ var currentAccess = Connectivity . NetworkAccess ;
202+ if ( currentAccess != initialAccess )
203+ {
204+ ReachabilityChanged ? . Invoke ( ) ;
205+ return ;
206+ }
189207 }
208+ ReachabilityChanged ? . Invoke ( ) ;
209+ }
210+ catch ( OperationCanceledException )
211+ {
212+ // Listener was disposed during polling, don't fire event
213+ }
214+ finally
215+ {
216+ Interlocked . Exchange ( ref pendingCallbacks , 0 ) ;
190217 }
191- ReachabilityChanged ? . Invoke ( ) ;
192218 }
193219 }
194220}
0 commit comments