feat: Introduce RegistryLoader class with Lazy<T> and async registry settings loading

- Added the RegistryLoader class to handle loading of registry settings.
- Implemented Lazy<T> for singleton pattern to ensure only one instance of RegistryLoader is created.
- Added support for asynchronous loading of registry settings to improve performance in async workflows.
- Designed the class with thread safety in mind, leveraging ConcurrentDictionary for storage.
- Included logging of debug messages to track the load process and timing.
This commit is contained in:
xRushG
2024-10-01 12:08:52 +02:00
parent 0a17220446
commit 9b28b7057d
3 changed files with 194 additions and 2 deletions

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Runtime.Versioning; using System.Runtime.Versioning;
@@ -8,6 +7,7 @@ using mRemoteNG.App.Info;
using mRemoteNG.App.Initialization; using mRemoteNG.App.Initialization;
using mRemoteNG.App.Update; using mRemoteNG.App.Update;
using mRemoteNG.Config.Connections.Multiuser; using mRemoteNG.Config.Connections.Multiuser;
using mRemoteNG.Config.Settings.Registry;
using mRemoteNG.Connection; using mRemoteNG.Connection;
using mRemoteNG.Messages; using mRemoteNG.Messages;
using mRemoteNG.Properties; using mRemoteNG.Properties;
@@ -22,6 +22,7 @@ namespace mRemoteNG.App
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public class Startup public class Startup
{ {
private RegistryLoader _RegistryLoader;
private AppUpdater _appUpdate; private AppUpdater _appUpdate;
private readonly ConnectionIconLoader _connectionIconLoader; private readonly ConnectionIconLoader _connectionIconLoader;
private readonly FrmMain _frmMain = FrmMain.Default; private readonly FrmMain _frmMain = FrmMain.Default;
@@ -30,7 +31,8 @@ namespace mRemoteNG.App
private Startup() private Startup()
{ {
_appUpdate = new AppUpdater(); _RegistryLoader = RegistryLoader.Instance; //created instance
_appUpdate = new AppUpdater();
_connectionIconLoader = new ConnectionIconLoader(GeneralAppInfo.HomePath + "\\Icons\\"); _connectionIconLoader = new ConnectionIconLoader(GeneralAppInfo.HomePath + "\\Icons\\");
} }

View File

@@ -0,0 +1,187 @@
using mRemoteNG.App;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading.Tasks;
namespace mRemoteNG.Config.Settings.Registry
{
[SupportedOSPlatform("windows")]
public class RegistryLoader : IDisposable
{
/// <summary>
/// This instance is used to load registry settings at program startup.cs, ensuring that
/// these settings are applied to regular configurations and can override them as needed.
/// Once the settings are loaded, this lazy instance will primarily be used for configuring
/// the options page. After this initialization phase, the lazy instance may no longer be necessary and get disposed.
/// </summary>
private static readonly Lazy<RegistryLoader> instance =
new(() => new RegistryLoader());
/// <summary>
/// Singleton instance of the RegistryMultiLoader class.
/// </summary>
public static RegistryLoader Instance => instance.Value;
/// <summary>
/// Dictionary to store settings for all registry pages.
/// </summary>
public static ConcurrentDictionary<Type, object> RegistrySettings { get; private set; }
/// <summary>
/// Static constructor that initializes the registry settings dictionary
/// and starts loading settings asynchronously at startup.
/// </summary>
static RegistryLoader()
{
RegistrySettings = new ConcurrentDictionary<Type, object>();
// Start loading asynchronously on startup
_ = LoadAllAsync();
}
/// <summary>
/// Cleans up the registry settings by removing entries of a specified type.
/// </summary>
/// <param name="deleteEntries">The type of entries to remove from the dictionary.</param>
/// <remarks>
/// If the dictionary becomes empty after removal and the singleton instance has been created,
/// it disposes of the instance to free resources.
/// </remarks>
public static void Cleanup(Type deleteEntries)
{
// Remove the registry setting from the dictionary
RegistrySettings?.TryRemove(deleteEntries, out _);
if (RegistrySettings.IsEmpty && instance.IsValueCreated)
{
Instance.Dispose();
}
}
/// <summary>
/// Asynchronously loads and applies multiple registry classes in parallel.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
/// <remarks>
/// This method creates a list of tasks, each responsible for initializing
/// and applying a specific registry class. It awaits the completion of all tasks
/// using Task.WhenAll.
/// </remarks>
private static async Task LoadAllAsync()
{
#if DEBUG
Stopwatch stopwatch = Stopwatch.StartNew();
#endif
try
{
// Create a list of tasks to initialize and apply each registry class asynchronously
var tasks = new List<Task>
{
LoadAndApplyAsync<OptRegistryAppearancePage>(),
LoadAndApplyAsync<OptRegistryConnectionsPage>(),
LoadAndApplyAsync<OptRegistryCredentialsPage>(),
LoadAndApplyAsync<OptRegistryNotificationsPage>(),
LoadAndApplyAsync<OptRegistrySecurityPage>(),
LoadAndApplyAsync<OptRegistrySqlServerPage>(),
LoadAndApplyAsync<OptRegistryStartupExitPage>(),
LoadAndApplyAsync<OptRegistryTabsPanelsPage>(),
LoadAndApplyAsync<OptRegistryUpdatesPage>()
};
// Await all tasks to complete
await Task.WhenAll(tasks);
Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
$"Registry settings loaded and applied asynchronously in {typeof(RegistryLoader).Name}", true);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
$"Registry settings error during load: {ex.Message}", true);
}
#if DEBUG
stopwatch.Stop();
Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
$"Registry settings total async load time: {stopwatch.ElapsedMilliseconds} ms", true);
#endif
}
/// <summary>
/// Asynchronously initializes and applies registry settings for a specified type.
/// </summary>
/// <typeparam name="T">The type of the registry settings class, which must have a parameterless constructor.</typeparam>
/// <returns>A task representing the asynchronous operation.</returns>
/// <remarks>
/// This method creates an instance of the specified type T, stores it in a dictionary
/// using its type as the key, and completes the task.
/// </remarks>
private static async Task LoadAndApplyAsync<T>() where T : new()
{
try
{
// create an instance of setting
var instance = new T();
// Store the instance in the dictionary using its type as key
RegistrySettings[typeof(T)] = instance;
// complete task
await Task.CompletedTask;
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
$"Registry settings error during load {typeof(T).Name}: {ex.Message}", true);
}
}
#region IDisposable Support
/// <summary>
/// Implements the IDisposable pattern for resource cleanup.
/// </summary>
private bool disposedValue = false; // To detect redundant calls
/// <summary>
/// Releases the resources used by the object.
/// </summary>
public void Dispose()
{
if (disposedValue)
return;
Dispose(true);
GC.SuppressFinalize(this);
disposedValue = true;
Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg,
$"Registry settings lazy instance of {typeof(RegistryLoader).Name} has been disposed.", true);
}
/// <summary>
/// Performs the actual resource cleanup.
/// </summary>
/// <param name="disposing">True if managed resources should be released.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
RegistrySettings?.Clear();
}
/// <summary>
/// Finalizer that is called to free unmanaged resources.
/// </summary>
~RegistryLoader()
{
Dispose(false);
}
#endregion IDisposable Support
}
}

View File

@@ -300,6 +300,9 @@ namespace mRemoteNG.Tools.WindowsRegistry
AllowedValues = allowedValues; AllowedValues = allowedValues;
} }
if (ElementType == typeof(string) && privateValue != null)
privateValue = EnforceStringInputValidity(privateValue);
return this; return this;
} }