Открытие окна WPF из C++/CLI с помощью асинхронного метода блокируется

Я пытаюсь открыть окно WPF, содержащее элемент управления WebView2, чтобы добавить аутентификацию OpenID Connect в существующее приложение C++ CLI.

Мы используем https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/tree/main/WpfWebView2/WpfWebView2 в качестве основы нашего кода.

Если вы не знакомы с этим примером, то его идея заключается в том, что существует «пользовательский» класс, представляющий «браузер». Этот класс отвечает за создание экземпляра окна WPF, добавление элемента управления WebView2 в качестве его содержимого, переход на сайт OAuth и возврат результатов аутентификации вызывающему объекту. Все это происходит при вызове его метода InvokeAsync().

Этот асинхронный метод вызывается классом OidcClient из библиотеки IdentityModel.OidcClient. Класс OidcClient принимает класс настроек, одним из которых является класс, реализующий его интерфейс IBrowser. Вы запускаете эту логику аутентификации, вызывая OidcClient.LoginAsync из своего приложения. В моем случае это код C++.

Когда я вызываю LoginAsync() из своего кода на C++, приложение блокируется и окно не открывается. Я почти уверен, что это проблема с потоком пользовательского интерфейса, но хоть убей, я не могу этого понять.

Я попробовал несколько подходов, включая попытку обернуть вызовы в Application.Current.Dispatcher.Invoke() и Application.Current.Dispatcher.BeginInvoke(). Я попытался определить, был ли я в теме пользовательского интерфейса, чтобы создать DispatcherSynchronizationContext.

Я подозреваю, что проблема в вызове async/await из C++, который начинается с работы, не связанной с пользовательским интерфейсом, и в какой-то момент должен выполнить работу с пользовательским интерфейсом. С помощью C++ я могу легко создавать экземпляры WPF Window (с помощью gcnew), а затем вызывать Show() или ShowDialog(), но этот многоуровневый дизайн async/await вызывает проблемы.

Я использую LoginAsync().Wait() со стороны C++, поэтому вполне возможно, что дело просто в неправильном вызове асинхронного метода из C++. Я даже не уверен, где еще искать и какие дополнительные знания мне нужны для отладки этой конкретной настройки.

В отношении WPF не существует такой вещи, как «вызов async/await из C++». async/await — это ключевые слова C#, которые запускают синтаксический сахар (преобразования компилятора). Современный стандарт C++ имеет свои собственные обещания и синтаксис ожидания, но я почти уверен, что они несовместимы с задачами .NET.

Ben Voigt 17.07.2024 03:07

В последнем абзаце предполагается, что вы вызываете Wait() для объекта Task .NET. Это просьба заблокировать.

Ben Voigt 17.07.2024 03:08

Согласен @BenVoigt. Чем больше я исследую это, тем больше я уверен, что моя основная проблема заключается в том, «как мне вызвать асинхронный метод C# из C++/CLI». У меня нет контроля над библиотекой IdentityModel.OidcClient, но мне нужно использовать ее для отображения окна входа в систему. И его методы асинхронны.

Jason 17.07.2024 04:54
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Вызов .Wait() наверняка вызывает тупик.

Если вам не нужен результат LoginAsync немедленно, вместо этого зарегистрируйте продолжение, используя .ContinueWith(...).

Если вам нужен синхронный результат LoginAsync, запустите новый DispatcherFrame в потоке пользовательского интерфейса. Код C# будет выглядеть так:

// Assuming that the 't' is a Task<...>.
var t = LoginAsync();

var frame = new DispatcherFrame();
t.ContinueWith((_) => { frame.Continue = false; }, TaskContinuationOptions.ExecuteSynchronously);

// Block current thread until LoginAsync is completed, while performing all UI stuff.
// (Make sure you call this in UI thread)
Dispatcher.PushFrame(frame);

// Since 't' is already completed, this call won't block the thread.
var loginResult = t.Result;

В C++/CLI нет удобного синтаксиса лямбда-выражений (теперь они есть, но они никогда не были интегрированы с поддержкой .NET, поэтому здесь бесполезны). Таким образом, OP придется создать явную вспомогательную функцию и явно создать экземпляр делегата (управляемой версии указателя функции) для передачи в ContinueWith. Но подход хороший.

Ben Voigt 17.07.2024 16:15

Спасибо вам обоим. Я поиграюсь с этим и отчитаюсь. Я не знаком с DispatcherFrame, поэтому мне нужно провести небольшое исследование.

Jason 17.07.2024 20:07

Поскольку мой первоначальный вопрос касался вызова асинхронного метода C# из C++/CLI без блокировки всего приложения, я опубликую здесь свой перенесенный код из ответа, предоставленного Sinus32. (Ему будет дана благодарность за ответ на сообщение.)

При этом я до сих пор не уверен на 100%, почему DispatcherFrame является решением, а не других диспетчеров, таких как Application.Current.Dispatcher...

// C# class that gathers OAuth settings from the app config file and ultimately 
// calls into IdentityModel.OidcClient to do the authentication
MyCompany::Client::Authentication::IAuthenticationService^ authService =
gcnew MyCompany::Client::Authentication::OidcAuthenticationService();

// PerformLoginAsync calls OidcClient.LoginAsync() which calls IBrowser.InvokeAsync() 
// which is responsible for creating the WPF Window with the embedded browser
//
// It is in here where I added code to switch to the WPF UI thread so
// the controls can be created.
auto loginTask = authService->PerformLoginAsync();

auto frame = gcnew System::Windows::Threading::DispatcherFrame();

// Since the callback needs to update the Continue method of DispatcherFrame and the
// callback is of Action<Task>, you need to treat frame variable as a "captured variable"
// and explicitly pass it in. It is my understanding that the dotnet compliler does this
// via a Field but I choose to do so via the ctor
auto helper = gcnew MyCompany::Client::Authentication::DispatcherFrameHelper(frame);

// This is needed since, as Ben Voight mentions, C++/CLI does not support lambdas.
auto callback = gcnew System::Action<System::Threading::Tasks::Task^>(helper, &MyCompany::Client::Authentication::DispatcherFrameHelper::SetDispatcherFrameToContinue);

loginTask->ContinueWith(callback, System::Threading::Tasks::TaskContinuationOptions::ExecuteSynchronously);

System::Windows::Threading::Dispatcher::PushFrame(frame);

auto result = loginTask->Result;
PushFrame чем-то похож на DoEvents из WinForms. Dispatcher и DispatcherFrame тесно связаны — просто посмотрите, как выглядит метод Dispatcher.Run(): referencesource.microsoft.com/#WindowsBase/Base/System/Windo‌​ws/… Во время работы Dispatcher имеет хотя бы один верхний фрейм. Запуск этого кадра — последнее, что делает Application.Run() после инициализации. И когда этот верхний кадр завершается, Dispatcher останавливается, Application.Run() завершается, static void Main() заканчивается и приложение закрывается.
Sinus32 20.07.2024 01:16

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