Блокировка UI при выполнении HTTP запроса
Стек: C#, MAUI, MVVM, DDD, Refit, Polly, Microsoft.Extensions.Http.Resilience, Microsoft.Extensions.DependencyInjection
Возникла проблема при выполнении HTTP к WebAPI. При выпадении ошибки блочится UI.
System.Threading.Tasks.TaskCanceledException: A task was canceled. at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.d__1[[System.Net.Http.HttpConnection, System.Net.Http, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]].MoveNext() at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.SocketsHttpHandler.g__CreateHandlerAndSendAsync|115_0(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.g__Core|4_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken) at Microsoft.Extensions.Http.Resilience.ResilienceHandler.<>c.<b__3_0>d.MoveNext()
Регистрация HttpClient
using GroupTracker.Application.Interfaces;
using GroupTracker.Infrastructure.Maui.Configuration;
using GroupTracker.Infrastructure.Maui.Interfaces;
using GroupTracker.Infrastructure.Maui.Local.Mappings;
using GroupTracker.Infrastructure.Maui.Persistence;
using GroupTracker.Infrastructure.Maui.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Resilience;
using Polly;
using Refit;
namespace GroupTracker.Infrastructure.Maui
{
public static class InfrastructureMauiDependencyInjection
{
public static IServiceCollection AddMauiInfrastructure(this IServiceCollection services)
{
services.AddDbContext<MauiDbContext>(options => options.UseSqlite(ConfigurationHelper.GetConnectionString()));
services.AddAutoMapper(cfg =>
{
}, typeof(DomainToInfrastructureMauiEntityProfile).Assembly);
services.AddSingleton<IAppSettingsService, AppSettingsService>();
services.AddRefitClient<IApiService>().ConfigureHttpClient((sp, client) =>
{
client.BaseAddress = new Uri(sp.GetRequiredService<IAppSettingsService>().GetBaseUrl());
client.Timeout = TimeSpan.FromSeconds(5);
}).AddResilienceHandler("api-pipeline", builder =>
{
builder.AddRetry(new HttpRetryStrategyOptions { MaxRetryAttempts = 3, BackoffType = DelayBackoffType.Exponential, UseJitter = true });
});
services.AddScoped<IMauiDbService, MauiDbService>();
services.AddSingleton<ITokenService, TokenService>();
return services;
}
}
}
ApiService
using GroupTracker.Application.DTOs;
using GroupTracker.Contracts.Responses;
using Refit;
namespace GroupTracker.Infrastructure.Maui.Interfaces
{
public interface IApiService
{
[Post("/Authentication/Verification")]
Task<Response<AuthResponseDto>> VerificationAsync([Body] UserCredentialsDto credentials);
[Post("/Users/AssignUniversity")]
Task<Response<bool>> AssignUniversityAsync(long userId, [Body] UserDto user);
[Get("/Universities")]
Task<Response<List<UniversityDto>>> GetUniversitiesAsync();
[Get("/Profiles")]
Task<Response<List<ProfileTypeDto>>> GetProfilesAsync();
}
}
Вызов API с клиента
namespace GroupTracker.MauiClient.ViewModels
{
[ChildOf(typeof(StartViewModel))]
public class RegistrationUserViewModel : BaseContentViewModel
{
private readonly IApiService _apiService;
private readonly IMapper _mapper;
private string _fullName;
private DateTime _age = DateTime.Today.AddYears(-18);
private List<ProfileTypeModel> _profiles;
private ProfileTypeModel _selectedProfile;
public RegistrationUserViewModel(IApiService apiService, IMapper mapper)
{
_apiService = apiService;
_mapper = mapper;
}
protected override async Task InitializeAsync() => await ExecuteWithLoadingAsync(async () =>
{
var profilesResult = await ExecuteRequestAsync(() => _apiService.GetProfilesAsync(), data => _mapper.Map<List<ProfileTypeModel>>(data), err => ErrorType = err) ?? new List<ProfileTypeModel>();
Profiles = profilesResult;
});
Обертки
protected async Task<TResult?> ExecuteRequestAsync<TResponse, TResult>(Func<Task<Response<TResponse>>> action, Func<TResponse, TResult> mapFunc, Action<ErrorType>? onError = null)
{
try
{
var result = await action().ConfigureAwait(false);
;
if (!result.IsSuccess)
{
onError?.Invoke(result.ErrorType);
return default;
}
if (result.Data == null)
{
onError?.Invoke(ErrorType.EmptyData);
return default;
}
return mapFunc(result.Data);
}
catch (TaskCanceledException ex)
{
if (!ex.CancellationToken.IsCancellationRequested || ex.InnerException is TimeoutException)
onError?.Invoke(ErrorType.ServerUnreachable);
else
onError?.Invoke(ErrorType.Default);
}
catch (HttpRequestException)
{
onError?.Invoke(ErrorType.ServerUnreachable);
}
catch (ApiException)
{
onError?.Invoke(ErrorType.ServerUnreachable);
}
catch (Exception)
{
onError?.Invoke(ErrorType.Default);
}
return default;
}
protected async Task ExecuteWithLoadingAsync(Func<Task> action)
{
Loading = true;
try
{
await action();
}
finally
{
Loading = false;
}
}
public virtual async Task OnAppearingAsync()
{
Task? taskToWait;
lock (_initLock)
{
if (_initializationTask != null)
taskToWait = _initializationTask;
else
taskToWait = InitializeAsyncSafe();
}
try
{
await taskToWait;
}
finally
{
lock (_initLock)
if (_initializationTask == taskToWait)
_initializationTask = null;
}
}
private async Task InitializeAsyncSafe()
{
try
{
await InitializeAsync();
}
catch
{
lock (_initLock)
_initializationTask = null;
throw;
}
}
protected virtual Task InitializeAsync() => Task.CompletedTask;
Фриз на UI При преобразовании gif фриз срезался. Когда анимация останавливается фриз примерно на пол секунды - секунду.
Ответы (1 шт):
Проблема в эмуляторе. HTTP не блокировал и все потоки корректно работали и обрабатывались. На локальном устройстве фриз пропал.
