UWP AppService 到 C++ SendRequestAsync 挂起/从未得到响应

Posted

技术标签:

【中文标题】UWP AppService 到 C++ SendRequestAsync 挂起/从未得到响应【英文标题】:UWP AppService to C++ SendRequestAsync hangs / never gets response 【发布时间】:2021-12-04 02:09:41 【问题描述】:

我这里有 2 个有问题的应用程序,一个是 C# UWP 的 UI 应用程序,它通过 LaunchFullTrustProcessForCurrentAppAsync API 启动后端 EXE(C++、WinRT)。后端 EXE 然后创建一个进程内 AppService 连接返回到 UI 以进行来回通信。组件建立连接,当 UWP UI App 调用 SendRequestAsync 时,后端在其 RequestReceived 回调中接收它。然后它构建一个响应并从 OnRequestReceived 返回。但是调用 UI SendRequestAsync() 永远不会返回,它永远挂起。

来自 UWP C# 应用的代码

 class FrontendAppService
    
        private AppServiceConnection AppServiceConnection  get; set; 
        private BackgroundTaskDeferral AppServiceDeferral  get; set; 

        public event EventHandler<string> MessageReceivedEvent;

        public bool Connected 
        
            get
            
                return AppServiceConnection != null;
            
        

        private static FrontendAppService instance;
        public static FrontendAppService Instance
        
            get
            
                if (instance == null)
                
                    instance = new FrontendAppService();
                

                return instance;
            
        

        private FrontendAppService()
        
            AppServiceConnection = null;
        

        public void BackgroundActivated(IBackgroundTaskInstance taskInstance)
        
            if (taskInstance.TriggerDetails is AppServiceTriggerDetails)
            

                AppServiceTriggerDetails appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
                AppServiceDeferral = taskInstance.GetDeferral();
                AppServiceConnection = appService.AppServiceConnection;
                AppServiceConnection.RequestReceived += OnAppServiceRequestReceived;
                AppServiceConnection.ServiceClosed += AppServiceConnection_ServiceClosed;
            
        

        private void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        
        

        private void AppServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        
            AppServiceDeferral.Complete();
            AppServiceConnection = null;
        

        public async Task<AppServiceResponse> SendRequestAsync(Windows.Foundation.Collections.ValueSet message)
        
            return await AppServiceConnection.SendMessageAsync(message);
        
    

来自 C++ 后端的代码

struct AppServiceServerImpl : winrt::implements<AppServiceServerImpl, IInspectable>

private:
    Windows::ApplicationModel::AppService::AppServiceConnection connection nullptr ;

    fire_and_forget OnServiceClosed(AppServiceConnection const&, AppServiceClosedEventArgs const&)
    
        LOG(AixLog::Severity::info) << "AppService connection lost" << std::endl;

        auto lifetime = get_strong();

        //Close the connection reference we're holding
        if (connection != nullptr)
        
            connection.Close();
            connection = nullptr;
        

        co_return;
    

    fire_and_forget OnRequestReceived(AppServiceConnection const&, AppServiceRequestReceivedEventArgs const& args)
    
        LOG(AixLog::Severity::info) << "AppService message received" << std::endl;

        //Get a deferral so we can use an awaitable API to respond to the message
        auto messageDeferral = args.GetDeferral();

        try
        

            ValueSet input = args.Request().Message();

            winrt::hstring action;
            winrt::hstring path;
            winrt::hstring body;
            winrt::com_array<winrt::hstring> headerNames;
            winrt::com_array<winrt::hstring> headerValues;

            // Parse out the HTTP data
            if( input.HasKey(L"action") )
                action = input.TryLookup(L"action").try_as<IReference<winrt::hstring>>().GetString();
            if (input.HasKey(L"path"))
                path = input.TryLookup(L"path").try_as<IReference<winrt::hstring>>().GetString();
            if (input.HasKey(L"body"))
                body = input.TryLookup(L"body").try_as<IReference<winrt::hstring>>().GetString();


            std::string url = "http://localhost:" + std::to_string(BackendConfig::GlobalBackendConfig.BackendApiPort) + "/" + winrt::to_string(path);
            Windows::Foundation::Uri uri winrt::to_hstring(url) ;
            Windows::Web::Http::HttpClient httpClient;

            std::wstring httpResponseBody;
            int httpStatusCode;
            std::wstring httpStatusText;

            // Always catch network exceptions for async methods
            try
            
                Windows::Web::Http::HttpResponseMessage httpResponseMessage;
                Windows::Web::Http::HttpStringContent postContent winrt::to_hstring(body) ;

                try
                
                    if (action == L"GET")
                    
                        httpResponseMessage = httpClient.GetAsync(uri).get();

                    
                    else
                    
                        LOG(AixLog::Severity::error) << "AppService unknown action received! " << winrt::to_string(action) << std::endl;
                        throw std::exception("AppService unknown action received!");
                    

                    httpResponseBody = httpResponseMessage.Content().ReadAsStringAsync().get();
                    httpStatusCode = (int)httpResponseMessage.StatusCode();
                    httpStatusText = httpResponseMessage.ReasonPhrase();
                
                catch (winrt::hresult_error const& ex)
                
                    httpResponseBody = ex.message();
                    httpStatusCode = 500;
                    httpStatusText = L"Exception handling request";
                
            
            catch (winrt::hresult_error const& ex)
            
                httpResponseBody = ex.message();
                httpStatusCode = 500;
                httpStatusText = L"Exception handling request";
            

            //Create the response
            ValueSet result;
            result.Insert(L"status_code", box_value(httpStatusCode));
            result.Insert(L"status_text", box_value(httpStatusText));
            result.Insert(L"body", box_value(httpResponseBody));

            //Send the response
            auto r = co_await args.Request().SendResponseAsync(result);
            if (r != AppServiceResponseStatus::Success)
            
                LOG(AixLog::Severity::info) << "Response send failed, status=" << (int)r << std::endl;
            
        
        catch (std::exception e)
        
            LOG(AixLog::Severity::error) << "Exception dealing with a response on AppService: " << e.what() << std::endl;
        

        LOG(AixLog::Severity::info) << "Response sent! " << std::endl;

        // Signal when complete
        try
        
            messageDeferral.Complete();
        
        catch( std::exception e )
        
            LOG(AixLog::Severity::error) << "Failed too complete deferral! " << e.what() << std::endl;
        
    


public:

    fire_and_forget ConnectToAppServiceAsync(std::string frontendAppFamily)
    
        auto lifetime = get_strong();
        bool connected = false;

        //Is a connection already open?
        if (connection != nullptr)
        
            LOG(AixLog::Severity::error) << "AppService connection already exists" << std::endl;
            co_return;
        

        //Set up a new app service connection
        connection = AppServiceConnection();
        connection.AppServiceName(L"com.frontend");
        connection.PackageFamilyName(winrt::to_hstring(frontendAppFamily));
        connection.ServiceClosed( get_weak(), &AppServiceServerImpl::OnServiceClosed );
        connection.RequestReceived( get_weak(), &AppServiceServerImpl::OnRequestReceived );

        while (!connected)
        
            AppServiceConnectionStatus status = co_await connection.OpenAsync();

            //"connection" may have been nulled out while we were awaiting.
            if (connection == nullptr)
            
                LOG(AixLog::Severity::error) << "AppService Connection was closed" << std::endl;
                co_return;
            

            //If the new connection opened successfully we're done here
            if (status == AppServiceConnectionStatus::Success)
            
                LOG(AixLog::Severity::info) << "AppService Connection is open" << std::endl;
                connected = true;
            
            else
            
                //Something went wrong. Lets figure out what it was and show the 
                //user a meaningful message
                switch (status)
                
                case AppServiceConnectionStatus::AppNotInstalled:
                    LOG(AixLog::Severity::error) << "The app AppServicesProvider is not installed. Reinstall on this device and try again." << std::endl;
                    break;

                case AppServiceConnectionStatus::AppUnavailable:
                    LOG(AixLog::Severity::error) << "The app AppServicesProvider is not available. This could be because it is currently being updated or was installed to a removable device that is no longer available." << std::endl;
                    break;

                case AppServiceConnectionStatus::AppServiceUnavailable:
                    LOG(AixLog::Severity::error) << "The app AppServicesProvider is installed but it does not provide the app service " << connection.AppServiceName().c_str() << std::endl;
                    break;

                default:
                case AppServiceConnectionStatus::Unknown:
                    LOG(AixLog::Severity::error) << "An unknown error occurred while we were trying to open an AppServiceConnection." << std::endl;
                    break;
                

                //Clean up before we go
                //connection.Close();
                //connection = nullptr;
            

            Sleep(500);
        
    

    void CloseAppServiceAsync()
    
        LOG(AixLog::Severity::info) << "AppService Connection is now closing" << std::endl;

        if (connection == nullptr)
        
            LOG(AixLog::Severity::error) << "There's no open connection to close" << std::endl;
            return;
        

        connection.Close();
        connection = nullptr;
    
;

就本示例而言,它只是复制一个 HTTP GET 请求,一切正常。后端的控制台输出如下所示:

Starting AppServiceServer Server
AppService Connection is open
AppService message received
Response sent!

从代码来看,C#调用

var response = await FrontendAppService.Instance.SendRequestAsync(input);

这是永远不会返回的函数。当后端应用程序在 OnRequestReceived 中完成延迟时,我会期望它返回?理想情况下是调用 Request().SendResponseAsync(result) 的内容?

【问题讨论】:

【参考方案1】:

在 C++ 桌面应用中调用 args.Request().SendResponseAsync(result); 方法后,请在 C# UWP 应用中处理 App.Connection.RequestReceived 事件。当消息从应用服务连接的另一个端点到达时,AppServiceConnection.RequestReceived event 将被触发。那是您可以获取从 C++ 应用程序发送的消息的地方。

像这样:

AppServiceConnection.RequestReceived += AppServiceConnection_RequestReceived;

private async void AppServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)

    var value = args.Request.Message["KEY"];
    // do you own logic

【讨论】:

我相信在上面的例子中函数设置为 OnAppServiceRequestReceived,我在 C# 端的那个函数中设置了一个断点,它从来没有被命中,所以我不认为它在这种情况下被调用C++ 代码正在响应请求。我的理解是,这只适用于不请自来的消息?我这么说是因为 C# 端的调用函数也永远不会返回。对 AppServiceConnection.SendMessageAsync() 的调用仍在等待调用者响应,因此该线程仍将永远阻塞。 好的。请您再做一个简单的测试来检查 C++ 组件是否可以成功发送应用服务请求?请使用SendMessageAsync()方法从C++组件主动发送应用服务请求。然后检查RequestReceived 事件是否被触发。 是的,C++ 端能够向 C# 端发送请求。值集结果; result.Insert(L"status_code", box_value(L"1")); auto rsp = co_await connection.SendMessageAsync(result);导致 OnAppServiceRequestReceived() 被接收,在 C# 端,响应(在 C# 中使用 Request.SendResponseAsync(response) 发送)然后在 C++ 端接收,就像我期望的那样,在上面的“rsp”变量中,它包含响应变量集的内容。 我觉得问题一定出在 C++ 方面。所以我在那里简化了几次函数,最简单的两个选项:``` fire_and_forget OnRequestReceived(AppServiceConnection const&, AppServiceRequestReceivedEventArgs const& args) auto messageDeferral = args.GetDeferral();值集结果; result.Insert(L"status_code", box_value(200)); co_await args.Request().SendResponseAsync(result); messageDeferral.Complete(); ``` 我尝试了使用和不使用 SendResponseAsync,C# 永远不会返回,永远挂起。 根据您的描述,C++端可以成功发送应用服务请求并收到来自C#端的响应,但该问题仅在C++组件发送响应时出现。那很有意思。普通的 C# 控制台应用程序怎么样,它可以正常工作吗?如果使用普通的 C# 控制台应用程序有效,则问题应该与您所说的 C++ 组件有关。

以上是关于UWP AppService 到 C++ SendRequestAsync 挂起/从未得到响应的主要内容,如果未能解决你的问题,请参考以下文章

UWP使用AppService向另一个UWP客户端应用程序提供服务

在 UWP 应用中创建使用调试 App Service (应用服务)

在 C++ 控制台应用程序中使用 UWP 库

单例 httpClient 到 AppService 的单个实例

如何在win32 C++控制台应用程序中调用uwp类库

使用 Win32 和 UWP/Xbox 平台支持管理 C++ 应用程序的最佳实践?