Вызов WPF SimpleInjector для client.GetAsync зависает

Я пытаюсь использовать SimpleInjector в приложении WPF (.NET Framework). Мы используем его точно так же во многих наших Сервисах, но по какой-то причине, когда я пытаюсь реализовать ту же логику в этом приложении WPF, вызов HttpClient().GetAsync зависает. Мы думаем, что это потому, что по какой-то причине Задача не выполняется.

Я регистрирую объекты из элемента OnStartUp файла App.xaml.cs, как показано ниже. Внутри конструктора SetupService мы вызываем URL-адрес SetupService (установленный в разделе SetupConfiguration файла App.Config), чтобы получить SetupResponse для использования в приложении.

В конечном итоге это зависает в методе ServiceClient.GetAsync, я попытался показать поток ниже:

Все классы, кажется, были введены правильно, и ServiceClient заполняется точно так же, как и та же точка в одном из наших рабочих сервисов. Мы в недоумении, что происходит и как это исправить.

Наконец, SetupService внедряется в другие классы, поэтому я предпочел бы заставить его работать так, а не удалять вызов из механизма SimpleInjector.

Любая помощь очень ценится.


public partial class App : Application
    {
        private static readonly Container _container = new Container();

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            RegisterDependencies();
            _container.Verify();

        }

        private void RegisterDependencies()
        {
            var serviceConfigSection = ServiceConfigurationSection.Get();
            
            _container.RegisterSingle<ILoggingProvider, LoggingProvider>();
            _container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
            _container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
            _container.RegisterSingle<ISetupService, SetupService>();

       }
}
   public class SetupService: ISetupService
    {
        private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
        {
            {"Accept", "application/xml"}
        };
        private const string AuthenticationType = "Basic";

        private readonly IServiceClient _serviceClient;
        private readonly ILoggingProvider _logger;
        private readonly IConfigurationSection _configuration;


        public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
        {
            _serviceClient = serviceClient;
            _logger = logger;
            _configuration = kmsConfiguration;

            RefreshSetup();
        }

        public void RefreshSetup()
        {
            try
            {
                var token = BuildIdentityToken();

                var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);

                var authenticationHeaderValue =
                    new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));

                _serviceClient.Url = _configuration.Url;
                var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;

                var responseString = httpResponse.Content.ReadAsStringAsync().Result;

                _response = responseString.FromXML<SetupResponse>();
            }
            catch (Exception e)
            {
                throw
            }
        }
    public class ServiceClient : IServiceClient
    {
        private const string ContentType = "application/json";
        private string _userAgent;

        private ILoggingProvider _logger;

        public string Url { get; set; }
        public string ProxyAddress { get; set; }
        public int TimeoutForRequestAndResponseMs { get; set; }
        public int HttpCode { get; private set; }

        public ServiceClient(ILoggingProvider logger = null)
        {
            _logger = logger;
        }


        public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(Url);
                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
                if (authenticationHeaderValue != null)
                    client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
                ProcessHeader(client.DefaultRequestHeaders, additionalData);
                var paramsQueryString = ProcessParams(additionalParams);
                if (!string.IsNullOrEmpty(paramsQueryString))
                    endpoint = $"{endpoint}?{paramsQueryString}";
                
                return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**

            }
        }
    }
}

Как вы думаете, почему SimpleInjector имеет какое-то отношение к этому? Почему вы думаете, что это потому, что задача не запускается? Скорее всего, это потому, что вы блокируете поток пользовательского интерфейса этими .Result вызовами.

Paulo Morgado 23.11.2022 20:27

Честно говоря, нет, я читал, что есть проблемы с SimpleInjector и WPF, поэтому я включил это в описание. Будет сложно удалить эти команды .Result, поскольку они находятся в производственном коде, который нормально работает в других средах. Но, по крайней мере, у меня есть направление, в котором нужно двигаться, спасибо за ваши комментарии :)

Andrew Humphries 24.11.2022 10:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Если вы блокируете асинхронный код из потока пользовательского интерфейса, вы можете ожидать взаимоблокировок. Я объясняю это полностью в своем блоге. В этом случае причиной взаимоблокировки является Result. Есть пара решений.

Я рекомендую переосмыслить ваш пользовательский опыт. Ваш пользовательский интерфейс не должен блокировать выполнение HTTP-вызова до того, как он что-либо покажет; вместо этого немедленно (и синхронно) отобразите пользовательский интерфейс (т. е. некоторый экран «загрузка...»), а затем обновите этот пользовательский интерфейс после завершения HTTP-вызова.

Другой - блокировать во время запуска. Для этого есть несколько выкроек. Ни один из них не работает во всех ситуациях, но один из них, который обычно работает, заключается в том, чтобы обернуть асинхронную работу в Task.Run, а затем заблокировать ее, например, var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult(); и аналогичные для других блокирующих вызовов.

Блокировка перед показом пользовательского интерфейса обычно считается плохим UX. Магазины приложений обычно запрещают это. Вот почему я рекомендую изменить UX. Вы можете найти такой подход полезным.

Ответ принят как подходящий

Спасибо за ваши ответы, я просто хотел синхронизировать решение, которое я выбрал. Для меня было рискованно изменить код в SetupService, чтобы удалить .Result, хотя это, вероятно, было правильным решением, так как я не хотел влиять на другие работающие Сервисы, используя уже существующую библиотеку SetupService.

В итоге я переместил регистрацию из потока пользовательского интерфейса, внедрив код SimpleInjector в библиотеку кода, создав Program.cs и Main() и установив его в качестве точки входа.

static class Program
    {
        public static readonly Container _container = new Container();
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        public static void Main(){

            var app = new MyApp.App();
            Register();
            app.Run(_container.GetInstance<MainWindow>());
        }

        static void Register()
        {
            _container.Register<MainWindow>();
            MySimpleInjector.Register(_container);
            _container.Verify();
        }
    }

а затем в отдельном проекте .dll MyApp.Common

    public class MySimpleInjector
    {
        private readonly Container _container;

        public static void Register(Container container)
        {
            var injector = new MySimpleInjector(container);
        }

        private void RegisterDependencies()
        {
            var serviceConfigSection = ServiceConfigurationSection.Get();
            
            _container.RegisterSingle<ILoggingProvider, LoggingProvider>();
            _container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
            _container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
            _container.RegisterSingle<ISetupService, SetupService>();

       }
    }

Я понимаю, что это может быть не идеальное решение, но оно подходит для моих целей.

Еще раз спасибо за помощь и комментарии! Андрей.

Другие вопросы по теме