Async Services

Visual Studio 2015+ supports asynchronous package loading, which improves startup performance by allowing extensions to initialize in the background. Understanding async patterns is essential for well-behaved extensions.

Why Async Loading?

Synchronous package loading blocks VS startup:

// Bad: Blocks VS startup
public sealed class MyPackage : Package
{
    protected override void Initialize()
    {
        // This runs on the main thread during VS startup
        base.Initialize();
        DoExpensiveWork(); // Blocks the UI!
    }
}

Async loading allows background initialization:

// Good: Doesn't block VS startup
public sealed class MyPackage : AsyncPackage
{
    protected override async Task InitializeAsync(
        CancellationToken cancellationToken,
        IProgress<ServiceProgressData> progress)
    {
        // This runs in the background
        await DoExpensiveWorkAsync();

        // Switch to main thread only when needed
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
        RegisterCommands();
    }
}

AsyncPackage

Basic Structure

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid("your-package-guid")]
public sealed class MyPackage : AsyncPackage
{
    protected override async Task InitializeAsync(
        CancellationToken cancellationToken,
        IProgress<ServiceProgressData> progress)
    {
        // Phase 1: Background work (no UI access)
        var config = await LoadConfigurationAsync();

        // Phase 2: Main thread work (UI access OK)
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
        InitializeUI(config);
    }
}
Note

AllowsBackgroundLoading = true is required for async loading. Without it, VS falls back to synchronous initialization.

Progress Reporting

Show loading progress in VS:

protected override async Task InitializeAsync(
    CancellationToken cancellationToken,
    IProgress<ServiceProgressData> progress)
{
    progress.Report(new ServiceProgressData("Loading configuration...", 1, 4));
    var config = await LoadConfigurationAsync();

    progress.Report(new ServiceProgressData("Initializing services...", 2, 4));
    await InitializeServicesAsync();

    progress.Report(new ServiceProgressData("Registering commands...", 3, 4));
    await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
    await RegisterCommandsAsync();

    progress.Report(new ServiceProgressData("Complete", 4, 4));
}

JoinableTaskFactory

The JoinableTaskFactory safely handles async/sync transitions:

Switching Threads

// Switch to main thread
await JoinableTaskFactory.SwitchToMainThreadAsync();

// Switch to background thread
await TaskScheduler.Default;

Running Async from Sync Code

When you must call async code from sync code:

// When you have a JoinableTaskFactory
JoinableTaskFactory.Run(async () =>
{
    await DoAsyncWorkAsync();
});

// From a package
ThreadHelper.JoinableTaskFactory.Run(async () =>
{
    await DoAsyncWorkAsync();
});
Warning

JoinableTaskFactory.Run should be used sparingly. It blocks the current thread waiting for async completion.

Async Services

Providing Async Services

[ProvideService(typeof(SMyService), IsAsyncQueryable = true)]
public sealed class MyPackage : AsyncPackage
{
    protected override async Task InitializeAsync(
        CancellationToken cancellationToken,
        IProgress<ServiceProgressData> progress)
    {
        AddService(typeof(SMyService), CreateServiceAsync, promote: true);
    }

    private async Task<object> CreateServiceAsync(
        IAsyncServiceContainer container,
        CancellationToken cancellationToken,
        Type serviceType)
    {
        // Create service on background thread if possible
        var service = new MyService();
        await service.InitializeAsync();
        return service;
    }
}

Consuming Async Services

// Async (preferred)
var service = await GetServiceAsync(typeof(SMyService)) as IMyService;

// With Community Toolkit
var service = await VS.GetServiceAsync<SMyService, IMyService>();

Background Tasks

Long-Running Operations

For operations that outlive a single method:

public sealed class MyPackage : AsyncPackage
{
    private CancellationTokenSource _backgroundTaskCts;

    protected override async Task InitializeAsync(
        CancellationToken cancellationToken,
        IProgress<ServiceProgressData> progress)
    {
        _backgroundTaskCts = new CancellationTokenSource();

        // Start background work that continues running
        _ = DoBackgroundWorkAsync(_backgroundTaskCts.Token);
    }

    private async Task DoBackgroundWorkAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
            await ProcessInBackgroundAsync();
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _backgroundTaskCts?.Cancel();
            _backgroundTaskCts?.Dispose();
        }
        base.Dispose(disposing);
    }
}

Fire-and-Forget with Error Handling

// Use JoinableTaskFactory for fire-and-forget
_ = JoinableTaskFactory.RunAsync(async () =>
{
    try
    {
        await DoWorkAsync();
    }
    catch (Exception ex)
    {
        await VS.StatusBar.ShowMessageAsync($"Error: {ex.Message}");
    }
});

UI Context Activation

Defer loading until specific conditions are met:

[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)]
public sealed class MyPackage : AsyncPackage
{
    protected override async Task InitializeAsync(
        CancellationToken cancellationToken,
        IProgress<ServiceProgressData> progress)
    {
        // Wait for solution to be fully loaded
        await KnownUIContexts.SolutionExistsAndFullyLoadedContext.WaitForActivationAsync();

        // Now safe to access solution services
        var solution = await VS.Solutions.GetCurrentSolutionAsync();
    }
}

Cancellation

Always handle cancellation properly:

protected override async Task InitializeAsync(
    CancellationToken cancellationToken,
    IProgress<ServiceProgressData> progress)
{
    // Check cancellation before expensive operations
    cancellationToken.ThrowIfCancellationRequested();

    // Pass token to async operations
    var data = await LoadDataAsync(cancellationToken);

    // Check again after long operations
    cancellationToken.ThrowIfCancellationRequested();

    await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
    ProcessData(data);
}

Best Practices

Do’s

// DO: Use async/await throughout
protected override async Task InitializeAsync(...)
{
    await InitializeServicesAsync();
}

// DO: Switch to main thread only when needed
await JoinableTaskFactory.SwitchToMainThreadAsync();
DoUIWork();

// DO: Handle cancellation
try
{
    await LongOperationAsync(cancellationToken);
}
catch (OperationCanceledException)
{
    // Expected during VS shutdown
}

Don’ts

// DON'T: Block on async code
var result = AsyncMethod().Result; // Deadlock risk!

// DON'T: Use Task.Run for VS APIs
await Task.Run(() => vsService.DoWork()); // Wrong thread!

// DON'T: Ignore cancellation tokens
await LongOperationAsync(); // Should pass cancellationToken

Complete Example

[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid(PackageGuids.MyPackageString)]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)]
[ProvideService(typeof(SMyService), IsAsyncQueryable = true)]
public sealed class MyPackage : AsyncPackage
{
    private IMyService _service;
    private CancellationTokenSource _cts;

    protected override async Task InitializeAsync(
        CancellationToken cancellationToken,
        IProgress<ServiceProgressData> progress)
    {
        _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

        // Phase 1: Background initialization
        progress.Report(new ServiceProgressData("Initializing service...", 1, 3));
        _service = new MyService();
        await _service.InitializeAsync(_cts.Token);
        AddService(typeof(SMyService), (_, _, _) => Task.FromResult<object>(_service), true);

        // Phase 2: Wait for VS to be ready
        progress.Report(new ServiceProgressData("Waiting for solution...", 2, 3));
        await KnownUIContexts.SolutionExistsAndFullyLoadedContext.WaitForActivationAsync();

        // Phase 3: Main thread initialization
        progress.Report(new ServiceProgressData("Registering commands...", 3, 3));
        await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
        await this.RegisterCommandsAsync();

        // Start background processing
        _ = ProcessSolutionInBackgroundAsync(_cts.Token);
    }

    private async Task ProcessSolutionInBackgroundAsync(CancellationToken cancellationToken)
    {
        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await _service.ProcessAsync(cancellationToken);
                await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
            }
        }
        catch (OperationCanceledException)
        {
            // Expected during shutdown
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _cts?.Cancel();
            _cts?.Dispose();
            (_service as IDisposable)?.Dispose();
        }
        base.Dispose(disposing);
    }
}

Next Steps

You’ve completed the Fundamentals section! Continue to Advanced Topics to learn about MEF components, language services, and more.