See the question and my original answer on StackOverflow

Since what causes Application.Run to return is the main form closing, the most simple not hacky way to do it for me seems to prevent the form from closing (duh).

For example with something like this:

internal static class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        var completedOperations = 0;
        var form = new Form();

        form.FormClosing += EarlyClosingBlocker.HandleFormClosing;
        form.Load += (s, e) => _ = EarlyClosingBlocker.Run(async () =>
        {
            await Task.Delay(1000);
            completedOperations++;
        });

        form.Shown += (s, e) => _ = EarlyClosingBlocker.Run(async () =>
        {
            await Task.Delay(2000);
            completedOperations++;
        });

        Application.Run(form);
        MessageBox.Show($"Completed operations: {completedOperations}");
    }
}

public static class EarlyClosingBlocker
{
    private static readonly ConcurrentDictionary<AutoResetEvent, object?> _waitEvents = new();

    public static async Task Run(Func<Task> action)
    {
        var are = new AutoResetEvent(false);
        _waitEvents[are] = null;
        try
        {
            await action();
        }
        finally
        {
            are.Set();
            _waitEvents.TryRemove(are, out _);
        }
    }

    public static void HandleFormClosing(object? sender, FormClosingEventArgs e)
    {
        var events = _waitEvents.ToArray();
        if (events.Length == 0)
            return;

        // note we could use e.CloseReason to decide not to wait whatsoever
        e.Cancel = true;
        var form = (Form)sender!;
        form.Hide(); // or put a message, etc.
        _ = Task.Run(() =>
        {
            // wait for all events to finish, note this cannot run in an STA thread
            WaitHandle.WaitAll([.. events.Select(e => e.Key)]);
            form.BeginInvoke(form.Close);
        });
    }
}