【问题标题】:UWP AppService to C++ SendRequestAsync hangs / never gets responseUWP AppService 到 C++ SendRequestAsync 挂起/从未得到响应
【发布时间】: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) 的内容?

【问题讨论】:

    标签: c# c++ uwp windows-runtime


    【解决方案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++ 组件有关。
    猜你喜欢
    • 2019-03-25
    • 2015-11-23
    • 2016-04-01
    • 2017-07-21
    • 2022-11-14
    • 2017-02-13
    • 2021-04-06
    • 1970-01-01
    • 2021-03-19
    相关资源
    最近更新 更多