Repository: microsoft/PowerToys
File: src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeManager.cs (C#)

Code before:
﻿// 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 SemaphoreSlim _initLock = new(1, 1);
    private static readonly TaskCompletionSource<ActionRuntime> _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);

    private static ActionRuntime _instance;
    private static bool _isInitialized;

    public static bool IsInitialized => _isInitialized;

    public static ActionRuntime Instance =>
        _isInitialized ? _instance : throw new InvalidOperationException("ActionRuntime has not been initialized yet. Call InitializeAsync first.");

    public static Task<ActionRuntime> WaitForRuntimeAsync() => _tcs.Task;

    public static async Task InitializeAsync()
    {
        if (_isInitialized)
        {
            return;
        }

        await _initLock.WaitAsync();

        try
        {
            if (_isInitialized)
            {
                return;
            }

            // 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++)

Review comment (suggestion):
Given anyway the caller need to "wait for initialization" before access this "Instance", I don't mean to create like AsyncLazy first (which not directly support in .net).

While, I will suggest to simplifiy to:
```cs
public static class ActionRuntimeManager
{
    // Lazy-initialized Task for async singleton
    private static readonly Lazy<Task<ActionRuntime>> _lazyRuntime = new(() => InitializeAsync());

    public static Task<ActionRuntime> InstanceAsync => _lazyRuntime.Value;

    private static async Task<ActionRuntime> InitializeAsync()
    {
        // Initialization logic (retries, etc.)
        // ...
    }
}
```
Then, your caller pattern is like:
1. _ = InstanceAsync;        // just to kick off the initialization, that's in IndexerCOmmandsProvider.cs
2. InstanceAsync.Result(); // Get the Runtime directly, that's in ActionListCOntextItems.cs

Code after:
﻿// 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;
    }
}