-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Android] Add NetworkChange implementation using PeriodicTimer (#80548)…
… (#81349) * Add NetworkChange implementation using PeriodicTimer * Code cleanup * Rename functions * Update csproj
- Loading branch information
1 parent
aea0710
commit 69a93b7
Showing
2 changed files
with
250 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
247 changes: 247 additions & 0 deletions
247
....Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Android.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Runtime.Versioning; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace System.Net.NetworkInformation | ||
{ | ||
public partial class NetworkChange | ||
{ | ||
private static readonly TimeSpan s_timerInterval = TimeSpan.FromSeconds(2); | ||
private static readonly object s_lockObj = new(); | ||
|
||
private static Task? s_loopTask; | ||
private static CancellationTokenSource? s_cancellationTokenSource; | ||
private static IPAddress[]? s_lastIpAddresses; | ||
|
||
public static event NetworkAddressChangedEventHandler? NetworkAddressChanged | ||
{ | ||
add | ||
{ | ||
if (value != null) | ||
{ | ||
lock (s_lockObj) | ||
{ | ||
if (s_addressChangedSubscribers.Count == 0 && | ||
s_availabilityChangedSubscribers.Count == 0) | ||
{ | ||
CreateAndStartLoop(); | ||
} | ||
else | ||
{ | ||
Debug.Assert(s_loopTask is not null); | ||
} | ||
|
||
s_addressChangedSubscribers.TryAdd(value, ExecutionContext.Capture()); | ||
} | ||
} | ||
} | ||
remove | ||
{ | ||
if (value != null) | ||
{ | ||
lock (s_lockObj) | ||
{ | ||
bool hadAddressChangedSubscribers = s_addressChangedSubscribers.Count != 0; | ||
s_addressChangedSubscribers.Remove(value); | ||
|
||
if (hadAddressChangedSubscribers && s_addressChangedSubscribers.Count == 0 && | ||
s_availabilityChangedSubscribers.Count == 0) | ||
{ | ||
StopLoop(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
public static event NetworkAvailabilityChangedEventHandler? NetworkAvailabilityChanged | ||
{ | ||
add | ||
{ | ||
if (value != null) | ||
{ | ||
lock (s_lockObj) | ||
{ | ||
if (s_addressChangedSubscribers.Count == 0 && | ||
s_availabilityChangedSubscribers.Count == 0) | ||
{ | ||
CreateAndStartLoop(); | ||
} | ||
else | ||
{ | ||
Debug.Assert(s_loopTask is not null); | ||
} | ||
|
||
s_availabilityChangedSubscribers.TryAdd(value, ExecutionContext.Capture()); | ||
} | ||
} | ||
} | ||
remove | ||
{ | ||
if (value != null) | ||
{ | ||
lock (s_lockObj) | ||
{ | ||
bool hadSubscribers = s_addressChangedSubscribers.Count != 0 || | ||
s_availabilityChangedSubscribers.Count != 0; | ||
s_availabilityChangedSubscribers.Remove(value); | ||
|
||
if (hadSubscribers && s_addressChangedSubscribers.Count == 0 && | ||
s_availabilityChangedSubscribers.Count == 0) | ||
{ | ||
StopLoop(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static void CreateAndStartLoop() | ||
{ | ||
Debug.Assert(s_cancellationTokenSource is null); | ||
Debug.Assert(s_loopTask is null); | ||
|
||
s_cancellationTokenSource = new CancellationTokenSource(); | ||
s_loopTask = Task.Run(() => PeriodicallyCheckIfNetworkChanged(s_cancellationTokenSource.Token)); | ||
} | ||
|
||
private static void StopLoop() | ||
{ | ||
Debug.Assert(s_cancellationTokenSource is not null); | ||
Debug.Assert(s_loopTask is not null); | ||
|
||
s_cancellationTokenSource.Cancel(); | ||
|
||
s_loopTask = null; | ||
s_cancellationTokenSource = null; | ||
s_lastIpAddresses = null; | ||
} | ||
|
||
private static async Task PeriodicallyCheckIfNetworkChanged(CancellationToken cancellationToken) | ||
{ | ||
using var timer = new PeriodicTimer(s_timerInterval); | ||
|
||
try | ||
{ | ||
while (await timer.WaitForNextTickAsync(cancellationToken).ConfigureAwait(false) | ||
&& !cancellationToken.IsCancellationRequested) | ||
{ | ||
CheckIfNetworkChanged(); | ||
} | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
} | ||
} | ||
|
||
private static void CheckIfNetworkChanged() | ||
{ | ||
var newAddresses = GetIPAddresses(); | ||
if (s_lastIpAddresses is IPAddress[] oldAddresses && NetworkChanged(oldAddresses, newAddresses)) | ||
{ | ||
OnNetworkChanged(); | ||
} | ||
|
||
s_lastIpAddresses = newAddresses; | ||
} | ||
|
||
private static IPAddress[] GetIPAddresses() | ||
{ | ||
var addresses = new List<IPAddress>(); | ||
|
||
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); | ||
foreach (var networkInterface in networkInterfaces) | ||
{ | ||
var properties = networkInterface.GetIPProperties(); | ||
foreach (var addressInformation in properties.UnicastAddresses) | ||
{ | ||
addresses.Add(addressInformation.Address); | ||
} | ||
} | ||
|
||
return addresses.ToArray(); | ||
} | ||
|
||
private static bool NetworkChanged(IPAddress[] oldAddresses, IPAddress[] newAddresses) | ||
{ | ||
if (oldAddresses.Length != newAddresses.Length) | ||
{ | ||
return true; | ||
} | ||
|
||
for (int i = 0; i < newAddresses.Length; i++) | ||
{ | ||
if (Array.IndexOf(oldAddresses, newAddresses[i]) == -1) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private static void OnNetworkChanged() | ||
{ | ||
Dictionary<NetworkAddressChangedEventHandler, ExecutionContext?>? addressChangedSubscribers = null; | ||
Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext?>? availabilityChangedSubscribers = null; | ||
|
||
lock (s_lockObj) | ||
{ | ||
if (s_addressChangedSubscribers.Count > 0) | ||
{ | ||
addressChangedSubscribers = new Dictionary<NetworkAddressChangedEventHandler, ExecutionContext?>(s_addressChangedSubscribers); | ||
} | ||
if (s_availabilityChangedSubscribers.Count > 0) | ||
{ | ||
availabilityChangedSubscribers = new Dictionary<NetworkAvailabilityChangedEventHandler, ExecutionContext?>(s_availabilityChangedSubscribers); | ||
} | ||
} | ||
|
||
if (addressChangedSubscribers != null) | ||
{ | ||
foreach (KeyValuePair<NetworkAddressChangedEventHandler, ExecutionContext?> | ||
subscriber in addressChangedSubscribers) | ||
{ | ||
NetworkAddressChangedEventHandler handler = subscriber.Key; | ||
ExecutionContext? ec = subscriber.Value; | ||
|
||
if (ec == null) // Flow suppressed | ||
{ | ||
handler(null, EventArgs.Empty); | ||
} | ||
else | ||
{ | ||
ExecutionContext.Run(ec, s_runAddressChangedHandler, handler); | ||
} | ||
} | ||
} | ||
|
||
if (availabilityChangedSubscribers != null) | ||
{ | ||
bool isAvailable = NetworkInterface.GetIsNetworkAvailable(); | ||
NetworkAvailabilityEventArgs args = isAvailable ? s_availableEventArgs : s_notAvailableEventArgs; | ||
ContextCallback callbackContext = isAvailable ? s_runHandlerAvailable : s_runHandlerNotAvailable; | ||
foreach (KeyValuePair<NetworkAvailabilityChangedEventHandler, ExecutionContext?> | ||
subscriber in availabilityChangedSubscribers) | ||
{ | ||
NetworkAvailabilityChangedEventHandler handler = subscriber.Key; | ||
ExecutionContext? ec = subscriber.Value; | ||
|
||
if (ec == null) // Flow suppressed | ||
{ | ||
handler(null, args); | ||
} | ||
else | ||
{ | ||
ExecutionContext.Run(ec, callbackContext, handler); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |