Я пытаюсь использовать 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 и WPF, поэтому я включил это в описание. Будет сложно удалить эти команды .Result, поскольку они находятся в производственном коде, который нормально работает в других средах. Но, по крайней мере, у меня есть направление, в котором нужно двигаться, спасибо за ваши комментарии :)
Если вы блокируете асинхронный код из потока пользовательского интерфейса, вы можете ожидать взаимоблокировок. Я объясняю это полностью в своем блоге. В этом случае причиной взаимоблокировки является 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>();
}
}
Я понимаю, что это может быть не идеальное решение, но оно подходит для моих целей.
Еще раз спасибо за помощь и комментарии! Андрей.
Как вы думаете, почему
SimpleInjector
имеет какое-то отношение к этому? Почему вы думаете, что это потому, что задача не запускается? Скорее всего, это потому, что вы блокируете поток пользовательского интерфейса этими.Result
вызовами.