[CmdPal] Refactor ActionRuntime initialization to avoid repeated delays on failure (#40229)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
This PR addresses an issue where ActionRuntime initialization may
repeatedly incur delays and fail during runtime usage, especially when
the OS supports the API but action creation fails.

### Fix:
Introduced ActionRuntimeManager to:

- Manage a shared static ActionRuntime instance
- Perform initialization once, at extension loading time
- Store the result (success or failure) to avoid repeated creation
attempts

Consumers now:

- ActionRuntimeManager.InstanceAsync.GetAwaiter().GetResult()

Initialization will only be attempted up to 3 times. After that, runtime
is marked as unavailable and no further attempts are made.
<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] **Closes:** #40228
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
This commit is contained in:
leileizhang
2025-06-26 23:50:17 +08:00
committed by GitHub
parent 1952a17a17
commit d65ba7f348
3 changed files with 83 additions and 13 deletions

View File

@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Windows.AI.Actions;
namespace Microsoft.CmdPal.Ext.Indexer.Data;
public static class ActionRuntimeManager
{
private static readonly Lazy<Task<ActionRuntime>> _lazyRuntime = new(InitializeAsync);
public static Task<ActionRuntime> InstanceAsync => _lazyRuntime.Value;
private static async Task<ActionRuntime> InitializeAsync()
{
// If we tried 3 times and failed, should we think the action runtime is not working?
// then we should not use it anymore.
const int maxAttempts = 3;
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
var runtime = ActionRuntimeFactory.CreateActionRuntime();
await Task.Delay(500);
return runtime;
}
catch (Exception ex)
{
Logger.LogError($"Attempt {attempt} to initialize ActionRuntime failed: {ex.Message}");
if (attempt == maxAttempts)
{
Logger.LogError($"Failed to initialize ActionRuntime: {ex.Message}");
}
}
}
return null;
}
}

View File

@@ -2,9 +2,11 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Foundation.Metadata;
namespace Microsoft.CmdPal.Ext.Indexer;
@@ -17,6 +19,10 @@ public partial class IndexerCommandsProvider : CommandProvider
Id = "Files";
DisplayName = Resources.IndexerCommandsProvider_DisplayName;
Icon = Icons.FileExplorer;
if (ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))
{
_ = ActionRuntimeManager.InstanceAsync;
}
}
public override ICommandItem[] TopLevelCommands()

View File

@@ -2,10 +2,12 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Indexer.Commands;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Properties;
@@ -15,7 +17,7 @@ using Windows.System;
namespace Microsoft.CmdPal.Ext.Indexer.Pages;
internal sealed partial class ActionsListContextItem : CommandContextItem
internal sealed partial class ActionsListContextItem : CommandContextItem, IDisposable
{
private readonly string fullPath;
private readonly List<CommandContextItem> actions = [];
@@ -41,20 +43,24 @@ internal sealed partial class ActionsListContextItem : CommandContextItem
private void UpdateMoreCommands()
{
try
lock (UpdateMoreCommandsLock)
{
lock (UpdateMoreCommandsLock)
if (actionRuntime == null)
{
if (actionRuntime == null)
{
actionRuntime = ActionRuntimeFactory.CreateActionRuntime();
Task.Delay(500).Wait();
}
actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed;
actionRuntime.ActionCatalog.Changed += ActionCatalog_Changed;
actionRuntime = ActionRuntimeManager.InstanceAsync.GetAwaiter().GetResult();
}
if (actionRuntime == null)
{
return;
}
actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed;
actionRuntime.ActionCatalog.Changed += ActionCatalog_Changed;
}
try
{
var extension = System.IO.Path.GetExtension(fullPath).ToLower(CultureInfo.InvariantCulture);
ActionEntity entity = null;
if (extension != null)
@@ -85,9 +91,20 @@ internal sealed partial class ActionsListContextItem : CommandContextItem
MoreCommands = [.. actions];
}
}
catch
catch (Exception ex)
{
actionRuntime = null;
Logger.LogError($"Error updating commands: {ex.Message}");
}
}
public void Dispose()
{
lock (UpdateMoreCommandsLock)
{
if (actionRuntime != null)
{
actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed;
}
}
}
}