Другое

Оптимизация производительности MAUI на мобильных данных

Откройте решения для оптимизации производительности вашего приложения MAUI на мобильных данных. Сократите задержку и улучшите надежность с помощью передовых методов управления сетью.

Проблема производительности при использовании мобильных данных в приложении MAUI

Описание проблемы

Обнаружена проблема, при которой некоторые пользователи испытывают значительные задержки при загрузке или отображении контента при использовании мобильных данных. Это поведение не наблюдается при подключении к Wi-Fi, где приложение работает корректно. Проблема наблюдалась в одном и том же физическом местоположении и через разных мобильных операторов, в то время как другие приложения не демонстрируют аналогичной проблемы в схожих условиях. В журналах ошибок исключения отсутствуют, что указывает на то, что проблема может быть связана с сетевой задержкой или прерывистой связью, без достижения порога для активации времени ожидания в приложении.

Реализация кода

Метод генерации QR-кода

csharp
public static async Task<string> GenerarQR(string token, string llave, string tipoQR)
{
    try
    {
        var url = $"{Config.Active.APISIAccessQR}/{token}/{llave}/{tipoQR}";

        //Se envia headers personalizados a la petición para que no se utilicen los predeterminados
        var headers = new Dictionary<string, string>()
        {
            ["Accept"] = "application/json"
        };

        var response = await HandlingWebRequestsAsync<SIAccessQRResponse>(HttpMethod.Get, url, headers: headers);

        if (response == null)
        {
            SendException.EnvioError(new Exception(), $"Respuesta NULA por parte del API Siaccess");

            return "N/E_QR";
        }

        if(string.IsNullOrEmpty(response.CodigoQR))
        {
            SendException.EnvioError(new Exception(), $"Respuesta VACIA/NULA del valor del código de accesso por parte del API Siaccess. VALOR:{response.CodigoQR ?? "NULL"}, URL:{url}");

            return "N/E_QR";
        }

        return response.CodigoQR;
    }
    catch (Exception ex)
    {
        SendException.EnvioError(ex, "Ocurrió un problema al generar el QR de la credencial virtual.");

        throw;
    }
}

Метод обработки веб-запросов

csharp
public static async Task<T> HandlingWebRequestsAsync<T>(HttpMethod method, string uri, object body = null, Dictionary<string, string> headers = null, string otherToken = "")
{
    // Verifica si hay conexión a Internet antes de realizar la petición
    #if ANDROID || IOS
        if (!HasRealInternetAsync())
            throw new HttpRequestException("No hay conexión real a Internet.");
    #endif

    try
    {
        object response = null;
        await GetToken.RefreshToken();
        using (var request = CreateWebRequest(method, uri, body, headers, otherToken))
        {
            response = await HttpClient.SendAsync(request);
        }

        if (typeof(HttpResponseMessage).IsAssignableFrom(typeof(T)))
        {
            return (T)response;
        }

        if (!(response as HttpResponseMessage).IsSuccessStatusCode)
        {
            return default;
        }

        //Si no se procede a devolver el contenido deserializado
        //Se obtiene el contenido de la respuesta y se transforma a texto para la lectura
        var content = await (response as HttpResponseMessage).Content.ReadAsStringAsync();

        //Cierra completamente la conexión del HttpClient
        (response as HttpResponseMessage).Dispose();

        //El contenido se deserealiza para convertirlo en la clase indicada (T)
        return JsonConvert.DeserializeObject<T>(content);
    }
    catch (Exception ex)
    {
        SendException.EnvioError(ex, "Ocurrió un problema al consumir la Api" + uri + " al parecer no tiene conectividad.");

        return default;
    }
}

Вопрос

Что может вызывать проблемы производительности при использовании мобильных данных в приложении MAUI, и как можно оптимизировать обработку сетевых запросов для снижения задержки и повышения надежности в мобильных сетях?

Значительные задержки при использовании мобильных данных в приложениях MAUI обычно вызваны проблемами с сетевой задержкой, неправильной обработкой HttpClient, недостаточными таймаутами и отсутствием механизмов кэширования. Текущая реализация показывает, что HttpClient создается внутри метода HandlingWebRequestsAsync, что создает постоянную перегрузку при установке новых соединений для каждого запроса. Кроме того, таймаут в 5 секунд может быть недостаточным для нестабильных мобильных сетей.

Содержание


Основные причины проблем с производительностью в мобильных сетях

Мобильные сети представляют уникальные проблемы, влияющие на производительность приложений MAUI:

  1. Переменная задержка: Мобильные сети имеют более высокие и переменные задержки по сравнению с фиксированными WiFi-соединениями
  2. Ограниченная пропускная способность: Особенно важно для приложений, передающих большие объемы данных
  3. Прерывистая подключаемость: Мобильные соединения могут колебаться или временно пропадать
  4. Операционные ограничения: Разные операторы имеют различные политики качества обслуживания

Согласно исследованиям, задержка, связанная с установкой сетевых соединений, является одним из главных узких мест в мобильных приложениях.


Оптимизация HttpClient и соединений

Наиболее критическая проблема в предоставленном коде - создание новых экземпляров HttpClient для каждого запроса:

csharp
// Текущий код - НЕЭФФЕКТИВЕН
public static async Task<T> HandlingWebRequestsAsync<T>(...)
{
    // HttpClient создается здесь, создавая перегрузку
    using (var request = CreateWebRequest(method, uri, body, headers, otherToken))
    {
        response = await HttpClient.SendAsync(request); // HttpClient также пересоздается
    }
}

Рекомендуемое решение:

csharp
// Паттерн Singleton или Injection для HttpClient
public class NetworkService
{
    private static readonly Lazy<HttpClient> _httpClient = 
        new Lazy<HttpClient>(() => new HttpClient(new SocketsHttpHandler
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(15),
            PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
            MaxConnectionsPerServer = 10
        }));

    public static HttpClient HttpClient => _httpClient.Value;
}

// Оптимизированное использование
public static async Task<T> HandlingWebRequestsAsync<T>(...)
{
    using (var request = CreateWebRequest(method, uri, body, headers, otherToken))
    {
        // Повторное использование существующего HttpClient
        response = await NetworkService.HttpClient.SendAsync(request);
    }
}

Как упоминается в рекомендациях, повторное использование HttpClient минимизирует перегрузку и задержку, связанные с установкой сетевых соединений.


Реализация кэширования и предзагрузки

Реализация механизмов кэширования критически важна для сокращения времени отклика в мобильных сетях:

csharp
public static class CacheService
{
    private static readonly Dictionary<string, (object Data, DateTime Expiry)> _cache = new();
    private static readonly TimeSpan _defaultCacheDuration = TimeSpan.FromMinutes(30);

    public static void AddToCache<T>(string key, T data, TimeSpan? expiry = null)
    {
        _cache[key] = (data, DateTime.UtcNow + (expiry ?? _defaultCacheDuration));
    }

    public static bool TryGetFromCache<T>(string key, out T data)
    {
        if (_cache.TryGetValue(key, out var cached) && cached.Expiry > DateTime.UtcNow)
        {
            data = (T)cached.Data;
            return true;
        }
        
        data = default;
        return false;
    }
}

// Интеграция с методом QR
public static async Task<string> GenerarQR(string token, string llave, string tipoQR)
{
    var cacheKey = $"QR_{token}_{llave}_{tipoQR}";
    
    if (CacheService.TryGetFromCache(cacheKey, out string cachedQR))
    {
        return cachedQR;
    }

    var url = $"{Config.Active.APISIAccessQR}/{token}/{llave}/{tipoQR}";
    // ... остальной код
    
    // Кэширование ответа
    CacheService.AddToCache(cacheKey, response.CodigoQR, TimeSpan.FromHours(1));
    
    return response.CodigoQR;
}

Реализация механизмов кэширования и предзагрузки помогает снизить задержку и улучшить производительность, минимизируя сетевые запросы.


Обработка таймаутов и повторных попыток

Текущий таймаут в 5 секунд недостаточен для мобильных сетей. Рекомендуется реализовать более длительные таймауты с механизмами повторных попыток:

csharp
public static async Task<T> HandlingWebRequestsAsync<T>(...)
{
    const int maxRetries = 3;
    const int initialDelayMs = 1000;
    const int timeoutSeconds = 30; // Увеличено с 5 секунд

    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
            
            using (var request = CreateWebRequest(method, uri, body, headers, otherToken))
            {
                // Использование ConfigureAwait(false) для лучшей производительности на мобильных устройствах
                response = await HttpClient.SendAsync(request, cts.Token)
                    .ConfigureAwait(false);
            }

            // Обработка ответа...
            return await ProcessResponse<T>(response);
        }
        catch (TaskCanceledException) when (!cts.IsCancellationRequested)
        {
            // Таймаут, реализация экспоненциального отката
            if (attempt == maxRetries - 1) throw;
            
            int delay = initialDelayMs * (int)Math.Pow(2, attempt);
            await Task.Delay(delay);
        }
        catch (HttpRequestException ex) when (IsNetworkError(ex))
        {
            // Ошибка сети, повторная попытка
            if (attempt == maxRetries - 1) throw;
            
            await Task.Delay(initialDelayMs * (attempt + 1));
        }
    }
    
    throw new OperationCanceledException("Достигнуто максимальное количество повторных попыток");
}

private static bool IsNetworkError(Exception ex)
{
    return ex is HttpRequestException || 
           ex.Message.Contains("no route to host") ||
           ex.Message.Contains("connection refused");
}

Как предлагается в сообществе, таймауты в 5 секунд могут быть слишком короткими для мобильных сетей и должны быть увеличены.


Оптимизация под платформу

Платформы iOS и Android требуют специфических оптимизаций:

Оптимизации для iOS

csharp
#if IOS
using UIKit;
#endif

public class PlatformNetworkOptimizer
{
#if IOS
    public static void ConfigureiOSNetworkSettings()
    {
        // Специфическая настройка для iOS
        NSUrlSessionConfiguration.SetDiscretionaryNetworkActivityIndicator(true);
        
        // Специальная обработка сокетов в iOS
        ServicePointManager.UseNagleAlgorithm = false;
        ServicePointManager.Expect100Continue = false;
    }
#endif

#if ANDROID
    public static void ConfigureAndroidNetworkSettings()
    {
        // Исключение Android Network On Main Thread
        Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);
    }
#endif
}

Проблемы с производительностью в iOS могут быть связаны с исключениями сокетов и путями хоста, требующими специфической настройки.


Мониторинг и диагностика

Реализация инструментов профилирования для выявления узких мест:

csharp
public static class PerformanceMonitor
{
    private static readonly Dictionary<string, Stopwatch> _timers = new();

    public static void StartTimer(string operation)
    {
        if (!_timers.ContainsKey(operation))
        {
            _timers[operation] = new Stopwatch();
        }
        _timers[operation].Start();
    }

    public static void StopTimer(string operation)
    {
        if (_timers.TryGetValue(operation, out var timer))
        {
            timer.Stop();
            var elapsed = timer.ElapsedMilliseconds;
            
            // Регистрация метрик производительности
            MetricsCollector.RecordNetworkOperation(operation, elapsed);
            
            if (elapsed > 5000) // Более 5 секунд
            {
                Logger.LogWarning($"Медленная операция: {operation} заняла {elapsed}мс");
            }
            
            _timers[operation].Reset();
        }
    }
}

// Интеграция с основным методом
public static async Task<T> HandlingWebRequestsAsync<T>(...)
{
    PerformanceMonitor.StartTimer("WebRequest");
    
    try
    {
        // ... логика запроса
        return await ExecuteRequest<T>(...);
    }
    finally
    {
        PerformanceMonitor.StopTimer("WebRequest");
    }
}

Крайне важно профилировать приложение, чтобы точно определить, что делает его медленным.


Общие лучшие практики

  1. Повторное использование HttpClient: Создавать один экземпляр и повторно использовать его во всем приложении
  2. Реализация стратегического кэширования: Кэшировать ответы для уменьшения количества сетевых запросов
  3. Подходящие таймауты: Использовать таймауты 30-60 секунд для мобильных сетей
  4. Механизмы повторных попыток: Реализовывать повторные попытки с экспоненциальным откатом
  5. Использование ConfigureAwait(false): Улучшает производительность в мобильных приложениях
  6. Пул соединений: Настраивать пул соединений для большей эффективности
  7. Приоритизация запросов: Реализовывать очередь приоритетов для критических запросов
  8. Сжатие данных: Использовать сжатие Gzip или Brotli для уменьшения размера передачи

Как рекомендует Microsoft, использование ConfigureAwait для создания кода без контекста улучшает производительность в мобильных приложениях.


Источники

  1. 27 Tips for .NET & MAUI Apps Faster
  2. Optimizing Performance in .NET MAUI Applications
  3. .NET MAUI Performance Optimization
  4. .NET MAUI Error & Performance Monitoring
  5. Improve app performance - Microsoft Learn
  6. r/dotnetMAUI - Performance optimization discussion
  7. GitHub MAUI Issues - Network connectivity problems
  8. Performance Improvements in .NET MAUI - .NET Blog

Заключение

Проблемы с производительностью при использовании мобильных данных в приложениях MAUI можно решить, реализовав следующие основные стратегии:

  • Повторно использовать HttpClient во всем приложении для минимизации перегрузки при установке соединений
  • Реализовать кэширование для уменьшения количества сетевых запросов
  • Увеличить таймауты до 30-60 секунд для учета мобильной задержки
  • Использовать механизмы повторных попыток с экспоненциальным откатом для прерывистых соединений
  • Реализовать ConfigureAwait(false) для лучшей производительности в потоках UI
  • Профилировать приложение для точного выявления узких мест

При применении этих оптимизаций приложение должно демонстрировать значительно улучшенную производительность в мобильных сетях, с более быстрыми временами отклика и большей надежностью даже в условиях переменной подключаемости.

Авторы
Проверено модерацией
Модерация