# Performance Patterns and Optimization - Centron Enterprise Application ## Overview This document provides comprehensive coverage of performance patterns and optimizations within the Centron .NET 8 enterprise application. Performance patterns encompass caching strategies, query optimization, lazy loading, asynchronous programming, resource management, and monitoring techniques that ensure optimal application performance under various load conditions. ## Performance Architecture ### **Performance Design Principles** The Centron application implements performance optimization through multiple strategies: 1. **Caching**: Multi-level caching from database to UI components 2. **Asynchronous Processing**: Non-blocking I/O operations and background tasks 3. **Query Optimization**: Efficient database queries with proper indexing 4. **Resource Management**: Proper disposal and pooling of expensive resources 5. **Lazy Loading**: On-demand data loading to reduce initial load times 6. **Background Processing**: Offload heavy operations to background services ## Caching Patterns ### **1. Multi-Level Caching Strategy** ```csharp public interface ICacheService { Task> GetAsync(string key) where T : class; Task SetAsync(string key, T value, TimeSpan? expiry = null) where T : class; Task RemoveAsync(string key); Task RemoveByPatternAsync(string pattern); Task ClearAsync(); } public class HybridCacheService : ICacheService { private readonly IMemoryCache _memoryCache; private readonly IDistributedCache _distributedCache; private readonly ILogger _logger; private readonly CacheConfiguration _config; public HybridCacheService( IMemoryCache memoryCache, IDistributedCache distributedCache, CacheConfiguration config) { _memoryCache = memoryCache; _distributedCache = distributedCache; _config = config; } public async Task> GetAsync(string key) where T : class { try { // Level 1: Memory Cache (fastest) if (_memoryCache.TryGetValue(key, out T cachedValue)) { return Result.AsSuccess(cachedValue); } // Level 2: Distributed Cache (Redis/SQL Server) var distributedValue = await _distributedCache.GetStringAsync(key); if (!string.IsNullOrEmpty(distributedValue)) { var deserializedValue = JsonSerializer.Deserialize(distributedValue); // Store in memory cache for faster subsequent access var memoryExpiry = TimeSpan.FromMinutes(_config.MemoryCacheMinutes); _memoryCache.Set(key, deserializedValue, memoryExpiry); return Result.AsSuccess(deserializedValue); } return Result.AsError("Cache miss"); } catch (Exception ex) { _logger.LogError(ex, "Cache get operation failed for key: {Key}", key); return Result.AsError($"Cache operation failed: {ex.Message}"); } } public async Task SetAsync(string key, T value, TimeSpan? expiry = null) where T : class { try { var cacheExpiry = expiry ?? TimeSpan.FromMinutes(_config.DefaultExpiryMinutes); // Set in memory cache _memoryCache.Set(key, value, TimeSpan.FromMinutes(_config.MemoryCacheMinutes)); // Set in distributed cache var serializedValue = JsonSerializer.Serialize(value); var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheExpiry }; await _distributedCache.SetStringAsync(key, serializedValue, options); return Result.AsSuccess(); } catch (Exception ex) { _logger.LogError(ex, "Cache set operation failed for key: {Key}", key); return Result.AsError($"Cache operation failed: {ex.Message}"); } } } // Cache-Aside Pattern Implementation public class CachedAccountRepository : IAccountRepository { private readonly IAccountRepository _baseRepository; private readonly ICacheService _cacheService; private readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(15); public CachedAccountRepository(IAccountRepository baseRepository, ICacheService cacheService) { _baseRepository = baseRepository; _cacheService = cacheService; } public async Task> GetAccountByIdAsync(int accountId) { var cacheKey = $"account:{accountId}"; // Try cache first var cachedResult = await _cacheService.GetAsync(cacheKey); if (cachedResult.Status == ResultStatus.Success) return cachedResult; // Cache miss - get from database var dbResult = await _baseRepository.GetAccountByIdAsync(accountId); if (dbResult.Status == ResultStatus.Success) { // Cache the result await _cacheService.SetAsync(cacheKey, dbResult.Data, _cacheExpiry); } return dbResult; } public async Task> SaveAccountAsync(Account account) { var result = await _baseRepository.SaveAccountAsync(account); if (result.Status == ResultStatus.Success) { // Update cache with new data var cacheKey = $"account:{account.I3D}"; await _cacheService.SetAsync(cacheKey, result.Data, _cacheExpiry); // Invalidate related cached data await InvalidateRelatedCacheAsync(account); } return result; } private async Task InvalidateRelatedCacheAsync(Account account) { // Remove account lists that might include this account await _cacheService.RemoveByPatternAsync("accounts:list:*"); await _cacheService.RemoveByPatternAsync($"customer:{account.CustomerNumber}:*"); } } ``` ### **2. Application-Level Caching Patterns** ```csharp public class CachedTableService { private readonly ICacheService _cacheService; private readonly ICachedTableRepository _repository; public async Task>> GetCachedDataAsync(string tableName) where T : class { var cacheKey = $"cached_table:{tableName}"; var cachedResult = await _cacheService.GetAsync>(cacheKey); if (cachedResult.Status == ResultStatus.Success) { var cachedData = cachedResult.Data; // Check if cache needs refresh var lastModified = await _repository.GetLastModifiedAsync(tableName); if (lastModified <= cachedData.CacheTime) { return Result>.AsSuccess(cachedData.Data); } } // Cache is stale or missing - refresh from database return await RefreshCachedDataAsync(tableName); } private async Task>> RefreshCachedDataAsync(string tableName) where T : class { var dataResult = await _repository.GetDataAsync(tableName); if (dataResult.Status != ResultStatus.Success) return dataResult; var cachedData = new CachedTableData { Data = dataResult.Data, CacheTime = DateTime.UtcNow, TableName = tableName }; // Cache with appropriate expiry based on table type var expiry = GetCacheExpiryForTable(tableName); await _cacheService.SetAsync($"cached_table:{tableName}", cachedData, expiry); return Result>.AsSuccess(cachedData.Data); } private TimeSpan GetCacheExpiryForTable(string tableName) { return tableName.ToLower() switch { "countries" => TimeSpan.FromHours(24), // Static reference data "currencies" => TimeSpan.FromHours(12), "products" => TimeSpan.FromMinutes(30), // More dynamic data "prices" => TimeSpan.FromMinutes(5), // Frequently changing _ => TimeSpan.FromHours(1) // Default }; } } // Background cache warming service public class CacheWarmupService : BackgroundService { private readonly ICachedTableService _cachedTableService; private readonly ILogger _logger; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { await WarmupCriticalCaches(); await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken); } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "Error during cache warmup"); await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); } } } private async Task WarmupCriticalCaches() { var criticalTables = new[] { "countries", "currencies", "taxrates", "paymentmethods" }; var warmupTasks = criticalTables.Select(async table => { try { await _cachedTableService.GetCachedDataAsync(table); _logger.LogDebug("Warmed up cache for table: {Table}", table); } catch (Exception ex) { _logger.LogError(ex, "Failed to warm up cache for table: {Table}", table); } }); await Task.WhenAll(warmupTasks); } } ``` ## Asynchronous Programming Patterns ### **1. Async/Await Best Practices** ```csharp public class OptimizedBusinessLogic : BaseBL { // Correct async pattern with ConfigureAwait public async Task>> GetAccountsAsync(AccountFilter filter) { try { // Use ConfigureAwait(false) in library code to avoid deadlocks var accounts = await _accountRepository .GetAccountsAsync(filter) .ConfigureAwait(false); return Result>.AsSuccess(accounts); } catch (Exception ex) { return Result>.AsError($"Failed to get accounts: {ex.Message}"); } } // Parallel processing for independent operations public async Task> GetAccountSummaryAsync(int accountId) { try { // Start all async operations concurrently var accountTask = _accountRepository.GetAccountByIdAsync(accountId); var contractsTask = _contractRepository.GetContractsByAccountAsync(accountId); var invoicesTask = _invoiceRepository.GetRecentInvoicesAsync(accountId); var paymentsTask = _paymentRepository.GetRecentPaymentsAsync(accountId); // Wait for all to complete await Task.WhenAll(accountTask, contractsTask, invoicesTask, paymentsTask) .ConfigureAwait(false); // Process results var account = await accountTask.ConfigureAwait(false); var contracts = await contractsTask.ConfigureAwait(false); var invoices = await invoicesTask.ConfigureAwait(false); var payments = await paymentsTask.ConfigureAwait(false); if (account.Status != ResultStatus.Success) return Result.FromResult(account); var summary = new AccountSummary { Account = account.Data, ActiveContracts = contracts.Data?.Where(c => c.IsActive).ToList() ?? new List(), RecentInvoices = invoices.Data ?? new List(), RecentPayments = payments.Data ?? new List(), TotalOutstanding = CalculateOutstanding(invoices.Data, payments.Data) }; return Result.AsSuccess(summary); } catch (Exception ex) { return Result.AsError($"Failed to get account summary: {ex.Message}"); } } // Batch processing with controlled concurrency public async Task> ProcessAccountsBatchAsync(List accountIds, int maxConcurrency = 10) { var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency); var results = new ConcurrentBag(); var errors = new ConcurrentBag(); var tasks = accountIds.Select(async accountId => { await semaphore.WaitAsync().ConfigureAwait(false); try { var result = await ProcessSingleAccountAsync(accountId).ConfigureAwait(false); results.Add(result); } catch (Exception ex) { errors.Add($"Account {accountId}: {ex.Message}"); } finally { semaphore.Release(); } }); await Task.WhenAll(tasks).ConfigureAwait(false); return Result.AsSuccess(new BatchProcessResult { SuccessfulResults = results.ToList(), Errors = errors.ToList(), TotalProcessed = accountIds.Count }); } } ``` ### **2. Background Task Processing Patterns** ```csharp public abstract class ManagedBackgroundService : BackgroundService { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; protected abstract string ServiceName { get; } protected abstract TimeSpan GetExecutionInterval(); protected abstract void ExecuteService(CancellationToken stoppingToken); protected virtual void InitializeService(CancellationToken stoppingToken) { } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Starting background service: {ServiceName}", ServiceName); try { InitializeService(stoppingToken); while (!stoppingToken.IsCancellationRequested) { var stopwatch = Stopwatch.StartNew(); try { using (var scope = _serviceProvider.CreateScope()) { ExecuteService(stoppingToken); } } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "Error executing background service: {ServiceName}", ServiceName); } stopwatch.Stop(); var executionTime = stopwatch.Elapsed; var interval = GetExecutionInterval(); // Ensure we don't execute too frequently var delay = interval - executionTime; if (delay > TimeSpan.Zero) { await Task.Delay(delay, stoppingToken); } } } finally { _logger.LogInformation("Background service stopped: {ServiceName}", ServiceName); } } } // Specialized cache update service public class CacheUpdateService : ManagedBackgroundService { private DateTime? _lastOptimizedUpdate = null; protected override string ServiceName => "CacheUpdateService"; protected override void ExecuteService(CancellationToken stoppingToken) { using (var session = new BLSession()) { // Regular cache updates session.GetBL().ExecuteCacheUpdates().ThrowIfError(); // Optimized cache updates (less frequent) if (_lastOptimizedUpdate == null || DateTime.Now - _lastOptimizedUpdate > TimeSpan.FromMinutes(1)) { session.GetBL().ExecuteOptimizedCacheUpdates().ThrowIfError(); _lastOptimizedUpdate = DateTime.Now; } } } protected override TimeSpan GetExecutionInterval() { return TimeSpan.FromSeconds(2); // High frequency for immediate updates } protected override void InitializeService(CancellationToken stoppingToken) { try { using var session = new BLSession(); session.GetBL().RepairIfNecessaryCacheStatistic(); } catch (Exception e) { _logger.LogError(e, "Failed to repair the cached statistic table"); } } } // Task queue background processor public class TaskQueueProcessor : BackgroundService { private readonly ITaskQueue _taskQueue; private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { var task = await _taskQueue.DequeueAsync(stoppingToken); // Process task in separate scope for proper DI cleanup using (var scope = _serviceProvider.CreateScope()) { await ProcessTaskAsync(task, scope.ServiceProvider); } } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "Error processing background task"); } } } private async Task ProcessTaskAsync(BackgroundTask task, IServiceProvider serviceProvider) { try { task.Status = TaskStatus.Processing; task.StartedAt = DateTime.UtcNow; var processor = serviceProvider.GetRequiredService(task.ProcessorType) as ITaskProcessor; var result = await processor.ProcessAsync(task.Data); task.Status = result.Status == ResultStatus.Success ? TaskStatus.Completed : TaskStatus.Failed; task.Result = result.Data; task.Error = result.Error; } catch (Exception ex) { task.Status = TaskStatus.Failed; task.Error = ex.Message; _logger.LogError(ex, "Task processing failed: {TaskId}", task.Id); } finally { task.CompletedAt = DateTime.UtcNow; await _taskQueue.UpdateTaskAsync(task); } } } ``` ## Database Query Optimization Patterns ### **1. Efficient Query Patterns** ```csharp public class OptimizedQueryRepository { private readonly ISession _session; // Projection query - only select needed fields public async Task>> GetAccountSummariesAsync(AccountFilter filter) { try { var query = _session.QueryOver() .Where(a => a.IsActive) .SelectList(list => list .Select(a => a.I3D).WithAlias(() => alias.Id) .Select(a => a.CompanyName).WithAlias(() => alias.CompanyName) .Select(a => a.CustomerNumber).WithAlias(() => alias.CustomerNumber) .Select(a => a.CreditLimit).WithAlias(() => alias.CreditLimit)) .TransformUsing(Transformers.AliasToBean()); // Apply filters if (filter.CustomerNumbers?.Any() == true) query.WhereRestrictionOn(a => a.CustomerNumber).IsIn(filter.CustomerNumbers); if (!string.IsNullOrEmpty(filter.CompanyNameFilter)) query.Where(Restrictions.InsensitiveLike("CompanyName", filter.CompanyNameFilter, MatchMode.Anywhere)); // Pagination if (filter.PageSize > 0) { query.Skip(filter.Page * filter.PageSize).Take(filter.PageSize); } var results = await Task.FromResult(query.List()); return Result>.AsSuccess(results.ToList()); } catch (Exception ex) { return Result>.AsError($"Query failed: {ex.Message}"); } } // Batch loading with proper joins public async Task>> GetAccountsWithContactsAsync(List accountIds) { try { // Use batch loading to avoid N+1 queries var accounts = await Task.FromResult( _session.QueryOver() .Fetch(a => a.Contacts).Eager .Fetch(a => a.Addresses).Eager .WhereRestrictionOn(a => a.I3D).IsIn(accountIds) .List()); return Result>.AsSuccess(accounts.ToList()); } catch (Exception ex) { return Result>.AsError($"Batch query failed: {ex.Message}"); } } // Optimized aggregation query public async Task> GetAccountStatisticsAsync() { try { var statistics = await Task.FromResult( _session.QueryOver() .SelectList(list => list .SelectCount(a => a.I3D).WithAlias(() => alias.TotalAccounts) .SelectSum(a => a.CreditLimit).WithAlias(() => alias.TotalCreditLimit) .SelectAvg(a => a.CreditLimit).WithAlias(() => alias.AverageCreditLimit)) .Where(a => a.IsActive) .TransformUsing(Transformers.AliasToBean()) .SingleOrDefault()); return Result.AsSuccess(statistics); } catch (Exception ex) { return Result.AsError($"Statistics query failed: {ex.Message}"); } } } // Lazy loading pattern for large datasets public class LazyLoadingService { public async Task>> GetPagedDataAsync( IQueryable baseQuery, int page, int pageSize, CancellationToken cancellationToken = default) { try { var totalCount = await baseQuery.CountAsync(cancellationToken); var items = await baseQuery .Skip(page * pageSize) .Take(pageSize) .ToListAsync(cancellationToken); return Result>.AsSuccess(new PagedResult { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize) }); } catch (Exception ex) { return Result>.AsError($"Paged query failed: {ex.Message}"); } } } ``` ### **2. Connection and Transaction Optimization** ```csharp public class OptimizedDataAccess { // Connection pooling and session management public class OptimizedSession : IDisposable { private readonly ISession _session; private readonly ITransaction _transaction; public OptimizedSession() { _session = SessionFactory.OpenSession(); _transaction = _session.BeginTransaction(IsolationLevel.ReadCommitted); } public async Task> ExecuteAsync(Func> operation) { try { var result = await operation(_session); await _transaction.CommitAsync(); return Result.AsSuccess(result); } catch (Exception ex) { await _transaction.RollbackAsync(); return Result.AsError($"Operation failed: {ex.Message}"); } } public void Dispose() { _transaction?.Dispose(); _session?.Dispose(); } } // Batch operations for better performance public async Task BatchInsertAsync(IEnumerable entities) where T : class { try { using var session = new OptimizedSession(); return await session.ExecuteAsync(async s => { var batchSize = 100; var batch = 0; foreach (var entity in entities) { await s.SaveAsync(entity); if (++batch % batchSize == 0) { await s.FlushAsync(); s.Clear(); } } await s.FlushAsync(); return "Success"; }); } catch (Exception ex) { return Result.AsError($"Batch insert failed: {ex.Message}"); } } } ``` ## Resource Management and Pooling Patterns ### **1. Object Pooling Pattern** ```csharp public class ObjectPool : IDisposable where T : class, IDisposable, new() { private readonly ConcurrentQueue _objects = new(); private readonly Func _objectFactory; private readonly Action _resetAction; private readonly int _maxSize; private int _currentCount = 0; public ObjectPool(Func objectFactory = null, Action resetAction = null, int maxSize = 100) { _objectFactory = objectFactory ?? (() => new T()); _resetAction = resetAction; _maxSize = maxSize; } public PooledObject Get() { if (_objects.TryDequeue(out T item)) { Interlocked.Decrement(ref _currentCount); return new PooledObject(item, this); } return new PooledObject(_objectFactory(), this); } internal void Return(T item) { if (_currentCount < _maxSize) { _resetAction?.Invoke(item); _objects.Enqueue(item); Interlocked.Increment(ref _currentCount); } else { item.Dispose(); } } public void Dispose() { while (_objects.TryDequeue(out T item)) { item.Dispose(); } } } public struct PooledObject : IDisposable where T : class, IDisposable { private readonly ObjectPool _pool; public T Object { get; } internal PooledObject(T obj, ObjectPool pool) { Object = obj; _pool = pool; } public void Dispose() { _pool.Return(Object); } } // Usage example - HTTP client pooling public class HttpClientPool { private static readonly ObjectPool _httpClientPool = new ObjectPool( () => new HttpClient(), client => { /* Reset client state */ }, 50); public static async Task> MakeRequestAsync(string url) { using var pooledClient = _httpClientPool.Get(); var client = pooledClient.Object; try { var response = await client.GetStringAsync(url); return Result.AsSuccess(response); } catch (Exception ex) { return Result.AsError($"Request failed: {ex.Message}"); } } } ``` ### **2. Memory Management Patterns** ```csharp public class MemoryOptimizedService { private readonly MemoryPool _memoryPool = MemoryPool.Shared; public async Task> ProcessLargeDataAsync(Stream dataStream) { using var owner = _memoryPool.Rent(8192); // Rent 8KB buffer var buffer = owner.Memory; try { var processedData = new ProcessedData(); int bytesRead; while ((bytesRead = await dataStream.ReadAsync(buffer)) > 0) { // Process data in chunks to avoid large allocations var chunk = buffer.Slice(0, bytesRead); ProcessChunk(chunk.Span, processedData); } return Result.AsSuccess(processedData); } catch (Exception ex) { return Result.AsError($"Processing failed: {ex.Message}"); } // Buffer is automatically returned to pool when owner is disposed } private void ProcessChunk(Span chunk, ProcessedData result) { // Process data without additional allocations for (int i = 0; i < chunk.Length; i++) { result.Checksum += chunk[i]; } result.BytesProcessed += chunk.Length; } } // Large object heap optimization public class LargeDataProcessor { public Result ProcessLargeDataset(IEnumerable items) { try { var result = new ProcessedResult(); // Process in batches to avoid LOH pressure const int batchSize = 1000; var batch = new List(batchSize); foreach (var item in items) { batch.Add(item); if (batch.Count == batchSize) { ProcessBatch(batch, result); batch.Clear(); // Clear instead of creating new list } } // Process remaining items if (batch.Count > 0) { ProcessBatch(batch, result); } return Result.AsSuccess(result); } catch (Exception ex) { return Result.AsError($"Large data processing failed: {ex.Message}"); } } private void ProcessBatch(List batch, ProcessedResult result) { // Process batch and update result foreach (var item in batch) { result.ProcessedCount++; result.TotalValue += item.Value; } // Force garbage collection after processing batch // Only do this for large batches to avoid performance impact if (batch.Count >= 1000) { GC.Collect(0, GCCollectionMode.Optimized); } } } ``` ## Performance Monitoring and Profiling Patterns ### **1. Performance Metrics Collection** ```csharp public class PerformanceMonitoringService { private readonly IMetricsCollector _metricsCollector; private readonly ILogger _logger; public async Task> MonitorOperationAsync( string operationName, Func>> operation, Dictionary additionalMetrics = null) { var stopwatch = Stopwatch.StartNew(); var startTime = DateTime.UtcNow; try { var result = await operation(); stopwatch.Stop(); var duration = stopwatch.Elapsed; // Collect performance metrics var metrics = new OperationMetrics { OperationName = operationName, Duration = duration, Success = result.Status == ResultStatus.Success, StartTime = startTime, EndTime = DateTime.UtcNow, AdditionalMetrics = additionalMetrics ?? new Dictionary() }; await _metricsCollector.RecordOperationAsync(metrics); // Log slow operations if (duration > TimeSpan.FromSeconds(5)) { _logger.LogWarning("Slow operation detected: {Operation} took {Duration}ms", operationName, duration.TotalMilliseconds); } return result; } catch (Exception ex) { stopwatch.Stop(); await _metricsCollector.RecordOperationAsync(new OperationMetrics { OperationName = operationName, Duration = stopwatch.Elapsed, Success = false, StartTime = startTime, EndTime = DateTime.UtcNow, Error = ex.Message }); return Result.AsError($"Operation {operationName} failed: {ex.Message}"); } } public async Task> GeneratePerformanceReportAsync(TimeSpan period) { try { var endTime = DateTime.UtcNow; var startTime = endTime - period; var metrics = await _metricsCollector.GetMetricsAsync(startTime, endTime); var report = new PerformanceReport { Period = period, TotalOperations = metrics.Count, SuccessfulOperations = metrics.Count(m => m.Success), AverageDuration = TimeSpan.FromMilliseconds(metrics.Average(m => m.Duration.TotalMilliseconds)), SlowestOperations = metrics.OrderByDescending(m => m.Duration).Take(10).ToList(), MostFrequentOperations = metrics.GroupBy(m => m.OperationName) .OrderByDescending(g => g.Count()) .Take(10) .ToDictionary(g => g.Key, g => g.Count()) }; return Result.AsSuccess(report); } catch (Exception ex) { return Result.AsError($"Failed to generate performance report: {ex.Message}"); } } } // Performance measurement attribute [AttributeUsage(AttributeTargets.Method)] public class MeasurePerformanceAttribute : Attribute { public string OperationName { get; set; } public bool LogSlowOperations { get; set; } = true; public int SlowThresholdMs { get; set; } = 1000; } // Performance interceptor public class PerformanceInterceptor { private readonly PerformanceMonitoringService _performanceService; public async Task InterceptAsync(MethodInvocationContext context) { var perfAttribute = context.Method.GetCustomAttribute(); if (perfAttribute == null) return await context.ProceedAsync(); var operationName = perfAttribute.OperationName ?? $"{context.TargetType.Name}.{context.Method.Name}"; return await _performanceService.MonitorOperationAsync(operationName, async () => { var result = await context.ProceedAsync(); return Result.AsSuccess(result); }); } } ``` ### **2. Resource Usage Monitoring** ```csharp public class ResourceMonitoringService : BackgroundService { private readonly ILogger _logger; private readonly IMetricsCollector _metricsCollector; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { await CollectResourceMetricsAsync(); await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "Error collecting resource metrics"); } } } private async Task CollectResourceMetricsAsync() { using var process = Process.GetCurrentProcess(); var metrics = new ResourceMetrics { Timestamp = DateTime.UtcNow, WorkingSet = process.WorkingSet64, PrivateMemory = process.PrivateMemorySize64, VirtualMemory = process.VirtualMemorySize64, ProcessorTime = process.TotalProcessorTime, ThreadCount = process.Threads.Count, HandleCount = process.HandleCount }; // Add GC metrics for (int generation = 0; generation <= GC.MaxGeneration; generation++) { metrics.GcCollections[generation] = GC.CollectionCount(generation); } metrics.TotalMemory = GC.GetTotalMemory(false); await _metricsCollector.RecordResourceMetricsAsync(metrics); // Alert on high resource usage if (metrics.WorkingSet > 1_000_000_000) // > 1GB { _logger.LogWarning("High memory usage detected: {WorkingSet}MB", metrics.WorkingSet / 1_000_000); } if (metrics.ThreadCount > 100) { _logger.LogWarning("High thread count detected: {ThreadCount}", metrics.ThreadCount); } } } ``` ## Performance Best Practices and Guidelines ### **1. General Performance Guidelines** - **Measure First**: Always measure performance before optimizing - **Profile Regularly**: Use profiling tools to identify bottlenecks - **Cache Strategically**: Cache expensive operations but avoid over-caching - **Optimize Database Access**: Use proper indexing and query optimization - **Manage Resources**: Properly dispose of resources and use pooling ### **2. Async Programming Guidelines** - **Use ConfigureAwait(false)**: In library code to avoid deadlocks - **Avoid Blocking**: Never use .Result or .Wait() on async methods - **Parallel Processing**: Use Task.WhenAll for independent operations - **Limit Concurrency**: Use SemaphoreSlim to control concurrent operations ### **3. Memory Management Guidelines** - **Avoid Large Object Heap**: Keep objects under 85KB when possible - **Use Object Pooling**: For frequently created/disposed objects - **Stream Processing**: Process large datasets in chunks - **Monitor GC Pressure**: Track garbage collection frequency and duration ### **4. Caching Guidelines** - **Cache Expiry**: Set appropriate expiry times based on data volatility - **Cache Invalidation**: Implement proper cache invalidation strategies - **Cache Levels**: Use multi-level caching for optimal performance - **Cache Monitoring**: Monitor cache hit rates and effectiveness ## Conclusion The Centron application implements comprehensive performance patterns that ensure: - **Scalability**: Multi-level caching and efficient resource management - **Responsiveness**: Asynchronous programming and background processing - **Efficiency**: Query optimization and memory management - **Monitoring**: Performance metrics collection and resource monitoring - **Maintainability**: Clear patterns and best practices for performance optimization These performance patterns provide the foundation for a high-performance enterprise application that can scale with growing user demands while maintaining optimal response times and resource utilization.