{"record":{"id":"61beb3d8d2b35291","repo":"babalae/better-genshin-impact","slug":"error-61beb3","errorCode":null,"errorMessage":"请先在启动页，启动截图器再使用本功能","messagePattern":"请先在启动页，启动截图器再使用本功能","errorType":"exception","errorClass":"NormalEndException","httpStatus":null,"severity":"warning","filePath":"BetterGenshinImpact/GameTask/TaskRunner.cs","lineNumber":154,"sourceCode":"        if (CancellationContext.Instance.IsCancellationRequested)\n        {\n            _logger.LogInformation(\"独立任务在启动阶段被取消: {Name}\", soloTask.Name);\n            CancellationContext.Instance.Clear();\n            return;\n        }\n        \n        await Task.Run(() => RunCurrentAsync(\n            async () => await soloTask.Start(CancellationContext.Instance.Cts.Token),\n            resetCancellationContext: false,\n            clearCancellationContextOnLockFailure: true));\n    }\n\n    public void Init()\n    {\n        if (!TaskContext.Instance().IsInitialized)\n        {\n            UIDispatcherHelper.Invoke(() => { Toast.Warning(\"请先在启动页，启动截图器再使用本功能\"); });\n            throw new NormalEndException(\"请先在启动页，启动截图器再使用本功能\");\n        }\n\n        // 清空实时任务触发器\n        TaskTriggerDispatcher.Instance().ClearTriggers();\n        \n        // 隐藏地图遮罩\n        UIDispatcherHelper.Invoke(() =>\n        {\n            if (MaskWindow.InstanceNullable() != null)\n            {\n                if (MaskWindow.Instance().DataContext is MaskWindowViewModel vm)\n                {\n                    vm.IsInBigMapUi = false;\n                }\n            }\n        });\n        VisionContext.Instance().DrawContent.ClearAll(); \n        ","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/GameTask/TaskRunner.cs#L136-L172","documentation":"A NormalEndException (with a preceding Toast.Warning) thrown by TaskRunner.Init() when TaskContext.Instance().IsInitialized is false. It marks the task as gracefully ended rather than crashed: the toast tells the user to start the screenshotter on the startup page first. NormalEndException is the project's convention for an expected, user-facing termination.","triggerScenarios":"Invoking a solo/realtime task through TaskRunner before the screenshot/capture context has been started on the startup page. Init() is the entry guard before clearing triggers and hiding the mask.","commonSituations":"Triggering a gameplay automation feature before initializing capture; clicking a task button immediately after app launch; the capture context was disposed or never started.","solutions":["Go to the startup page and start the screenshotter before using any task feature.","If already started, restart BetterGI (the context may be in a bad state).","At the call site, check TaskContext.Instance().IsInitialized and disable/hide the feature until initialized.","Treat NormalEndException as expected termination at the UI boundary (do not log it as an error)."],"exampleFix":"// before\nTaskRunner.Instance().Init();\n// after\nif (!TaskContext.Instance().IsInitialized)\n{\n    Toast.Warning(\"请先在启动页，启动截图器再使用本功能\");\n    return;\n}\nTaskRunner.Instance().Init();","handlingStrategy":"validation","validationCode":"if (!TaskContext.Instance().IsInitialized)\n{\n    Toast.Warning(\"请先在启动页，启动截图器再使用本功能\");\n    return;\n}","typeGuard":"static bool IsReady => TaskContext.Instance().IsInitialized;","tryCatchPattern":"// NormalEndException is expected termination; handle at the UI boundary\ntry { TaskRunner.Instance().Init(); }\ncatch (NormalEndException) { /* user already got a toast */ }","preventionTips":["Start the screenshotter on the startup page before any task.","Treat NormalEndException as expected, not as an error to log loudly.","Gate task UI on IsInitialized."],"tags":["precondition","task-context","initialization","normal-end"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}