VsixCommunity / Community.VisualStudio.Toolkit

Making it easier to write Visual Studio extensions
Other
249 stars 44 forks source link

Slow performance with VS.Documents.OpenAsync method #407

Closed Rickinio closed 1 year ago

Rickinio commented 1 year ago

I have the following code in my extension: I try to open 22 files (a mix of .cs. .xaml and .resx files) and it takes around 17 seconds which is way too slow for the extension to have any usefulness. Is this something expected?

 var sp = new Stopwatch();
 sp.Start();
 var files = filesChanged.Select(f => $"{currentSolutionPath}\\{f.Replace("/", "\\")}");
 var tasks = files.Select(f => VS.Documents.OpenAsync(f)).ToArray();
 await Task.WhenAll(tasks);
 sp.Stop();
 Debug.WriteLine($"Elapsed ms was : {sp.ElapsedMilliseconds}");

This repo contains the full code in case it is needed.

reduckted commented 1 year ago

Are you running with the debugger attached? If so, it will be slower than without the debugger attached.

Using that code I can open 23 C# documents in about 3 seconds. With the debugger attached the time goes up to about 7 seconds.

Any files with a designer (.resx and .xaml) are also likely to take a bit longer than "text" files. If I include a couple of XAML files in my test, the time increases by about a second or two.

Another thing to keep in mind is that although OpenAsync is an asynchronous method, the only part that's actually asynchronous is switching to the main thread and resolving the services. The actual opening of the document will be synchronous:

https://github.com/VsixCommunity/Community.VisualStudio.Toolkit/blob/e0d1596cc2a35df289b08a1a2cc08d67bb2d7ac4/src/toolkit/Community.VisualStudio.Toolkit.Shared/Documents/Documents.cs#L60-L62

One thing you could do to improve the user experience is to show a "threaded wait dialog" while the files are being opened. Here's an example:

Stopwatch timer = new();
timer.Start();

IVsThreadedWaitDialogFactory factory = (IVsThreadedWaitDialogFactory)await VS.Services.GetThreadedWaitDialogAsync();
ThreadedWaitDialogProgressData progress = new(
    "Opening documents",
    "",
    "",
    isCancelable: true,
    currentStep: 0,
    totalSteps: filesChanged.Count
);

using (ThreadedWaitDialogHelper.Session session = factory.StartWaitDialog(Vsix.Name, initialProgress: progress))
{
    for (int i = 0; i < filesChanged.Count; i++)
    {
        if (session.UserCancellationToken.IsCancellationRequested)
        {
            break;
        }

        progress = new ThreadedWaitDialogProgressData(
            progress.WaitMessage,
            filesChanged[i],
            progress.StatusBarText,
            progress.IsCancelable,
            i,
            progress.TotalSteps
        );

        session.Progress.Report(progress);

        await VS.Documents.OpenAsync(Path.Combine(currentSolutionPath, filesChanged[i]));
    }
}

timer.Stop();
await VS.MessageBox.ShowAsync($"{filesChanged.Count} documents opened in {timer.ElapsedMilliseconds}ms.");

And here's the result (note that you can see it taking longer to open the .xaml and .resx files):

open

Rickinio commented 1 year ago

Hi @reduckted Thank you very much for your help. Much appreciated :) I knew debugger was adding an overhead but wasn't expecting so big difference. You were right without debugger attached the time is now 5 seconds. I will add the progress dialog as you suggested as well.

Again, thank you for your time