{"record":{"id":"4de62cd5454d63fd","repo":"babalae/better-genshin-impact","slug":"webview-targetprocessid","errorCode":null,"errorMessage":"WebView 进程 {targetProcessId} 当前不在线。","messagePattern":"WebView 进程 (.+?) 当前不在线。","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"warning","filePath":"BetterGenshinImpact/Service/Instance/InstanceService.cs","lineNumber":214,"sourceCode":"        JToken? data = null,\n        CancellationToken cancellationToken = default)\n    {\n        if (string.IsNullOrWhiteSpace(operation))\n        {\n            throw new ArgumentException(\"WebView 操作名称不能为空。\", nameof(operation));\n        }\n        if (Context.InstanceType == BetterGiInstanceType.WebView)\n        {\n            throw new InvalidOperationException(\"WebView 不能向其他 WebView 发送消息。\");\n        }\n\n        if (Context.InstanceType == BetterGiInstanceType.Primary)\n        {\n            if (!_messageState.WebViewConnectionsByProcessId.TryGetValue(\n                    targetProcessId,\n                    out var target))\n            {\n                throw new InvalidOperationException(\n                    $\"WebView 进程 {targetProcessId} 当前不在线。\");\n            }\n\n            var targetResponse = await target.Connection.SendRequestAsync(\n                InstanceOperations.WebViewMessage,\n                new WebViewMessage\n                {\n                    SourceProcessId = Context.ProcessId,\n                    Operation = operation,\n                    Data = data\n                },\n                RequestTimeout,\n                cancellationToken).ConfigureAwait(false);\n            EnsureSuccessfulResponse(targetResponse);\n            return;\n        }\n\n        var rootConnection = GetRequiredRootConnection();","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/Service/Instance/InstanceService.cs#L196-L232","documentation":"Thrown by SendWebViewMessageAsync on a Primary instance when the target process ID is not found in _messageState.WebViewConnectionsByProcessId. This dictionary tracks WebView processes that have completed connection.open registration with the root. A missing entry means the WebView process never connected, already disconnected, or crashed.","triggerScenarios":"A Primary instance calls SendWebViewMessageAsync with a targetProcessId that does not correspond to any currently-registered WebView connection. The WebView may have been listed by a prior GetVisibleWebViewsAsync call but disconnected between the list and the send, or the caller passed a stale or incorrect process ID.","commonSituations":"The WebView process crashed or was closed by the user between enumeration and message send; a race condition where the WebView disconnected during shutdown; the caller passed a PID from a different user session or a process that is not a BetterGI WebView; the root's ConnectionClosed handler removed the entry before the send completed.","solutions":["Re-enumerate WebViews via GetVisibleWebViewsAsync immediately before sending, and handle the case where the target is gone.","Catch InvalidOperationException and present a user-facing message ('目标 WebView 已离线') rather than crashing.","If the target PID comes from user selection in a UI, refresh the list periodically or on focus.","Verify the targetProcessId matches a process returned by GetVisibleWebViewsAsync, not an arbitrary OS process ID."],"exampleFix":"// before\nawait instanceService.SendWebViewMessageAsync(selectedPid, op, data);\n\n// after\ntry\n{\n    await instanceService.SendWebViewMessageAsync(selectedPid, op, data);\n}\ncatch (InvalidOperationException) when (instanceService.Context.InstanceType == BetterGiInstanceType.Primary)\n{\n    _logger.LogWarning(\"目标 WebView {Pid} 已离线，刷新列表\", selectedPid);\n    await RefreshWebViewListAsync();\n}","handlingStrategy":"try-catch","validationCode":"// Verify target is still registered before sending (Primary only)\nif (Context.InstanceType == BetterGiInstanceType.Primary)\n{\n    var webviews = await GetVisibleWebViewsAsync(cancellationToken);\n    if (!webviews.Any(w => w.ProcessId == targetProcessId))\n    {\n        // Target no longer available\n        return;\n    }\n}\nawait instanceService.SendWebViewMessageAsync(targetProcessId, operation, data);","typeGuard":null,"tryCatchPattern":"try\n{\n    await instanceService.SendWebViewMessageAsync(pid, operation, data);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"不在线\"))\n{\n    _logger.LogWarning(\"WebView {Pid} 已离线\", pid);\n    await RefreshWebViewListAsync();\n}","preventionTips":["Re-enumerate WebViews before sending if the list may be stale.","Handle the offline case gracefully in UI code with a refresh prompt.","Remember there is a race between enumeration and send — always catch."],"tags":["webview","connection-state","race-condition","instance-topology"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}