VSTHRD010: Accessing item should only be done on the main thread
Asked Answered
M

1

11

Microsoft changed VSIX to asynchronous with VS2019 and with that came grief.

I see many warnings that say:

Warning VSTHRD010 Accessing "Project" should only be done on the main thread. Call Microsoft.VisualStudio.ProjectSystem.IProjectThreadingService.VerifyOnUIThread() first.

I did add, though thinking of removing, the Nuget package Microsoft.VisualStudio.ProjectSystem, not that that helped.

Adding that line to the top of a method yields:

Error CS0120 An object reference is required for the non-static field, method, or property 'IProjectThreadingService.VerifyOnUIThread()'

A search was not helpful to resolve this last error.

I tried adding:

`Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();

but that did not change things.

Thoughts?

UPDATE

I presume the close vote was from the user that said to give code. There is no "code". Anyone who has done an upgrade from prior to VS2019 to VS2019 and ran into the synchronous VSIX to asynchronous VSIX will understand the issue. One cannot just use items like before. There are many of these warnings splattered because VS now classifies many elements as "Main UI Accessible" only. That means

if (null == oItems || 0 == oItems.Count)
   return false;

no longer works. oItems is defined as EnvDTE.ProjectItems. Hence the warningAccessing ProjectItems. Basically, anything inEnvDTE` is off limits.

The code will be anything that touches/uses EnvDTE objects.

RESPONSE FOR ANSWER 1

I implemented answer and got thrown a new error.

private void MenuItemCallback(object sender, EventArgs e)
{
info VSTHRD102 -> this.packageVsi.JoinableTaskFactory.Run(async () =>
    {
        await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
        this.EventOptionsDialog();
    });
}

private Task EventOptionsDialog()
{
    _ = System.Threading.Tasks.Task.Run(async () =>
    {
        await this.packageVsi.DoTask1Async();
    }
    );

    // Other tasks

    return null;
}

The informational message that comes back is:

Severity VSTHRD102 Limit use of synchronously blocking method calls such as JoinableTaskFactory.Run or Task.Result to public entrypoint members where you must be synchronous. Using it for internal members can needlessly add synchronous frames between asynchronous frames, leading to threadpool exhaustion.

  1. I want clean working code, so no messages, warnings, or errors.
  2. There is a heck of a lot of code that needs a solution, so if this technique exhausts the thread pool, then this answer is not good.
  3. There is a lot of downstream methods and from what I observed, VS2019 is stupid and does not know that a sub-method already has the main UI, though. The process complicates a bit, as some methods get called from multiple locations.
  4. I still want clean code without having many new code blocks added.
Memoir answered 21/8, 2019 at 18:9 Comment(1)
Have you managed to resolve this? If I can't find a solution, I'll add code to ignore that warning. I'm already ignoring other warnings that cannot be fixed. That's stupid.Bainbridge
J
8

When migrating to the AsyncPackage or other new async contexts, it might be easier to switch to the Main thread rather than assert/throw if you're not on it. This is usually done with by awaiting JoinableTaskFactory.SwitchToMainThreadAsync().

JoinableTaskFactory is inherited from AsyncPackage, or can also be accessed through Microsoft.VisualStudio.Shell.ThreadHelper.

If you're in a synchronous method that is no longer on the UI thread and wish to delegate to it, a common pattern is:

// Fire-and-forget
JoinableTaskFactory.RunAsync(async () =>
{
    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
    DoWork();
});

// Synchronously block the current thread and wait.
// Not recommended - can lead to thread pool starvation if over-used.
JoinableTaskFactory.Run(async () =>
{
    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
    DoWork();
});

Way more information about this topic can be found in the VS Threading Cookbook.

EDIT: a response to a response...

The VSTHRD102 errors are from using synchronous waits. As I mentioned, JTF.Run() blocks the current thread while work is done on the UI thread (ditto for task.Wait() or task.Result). Basically you're consuming 2 threads while only actually utilizing one, hence the analyzer complains (do this too often and the thread pool will be depleted as all the threads end up in a synchronous blocked state).

The threading design changes rolling out in VS will cause you to either:

  • Convert your methods to async Task<T> so that you can await JTF.RunAsync() (or await JTF.SwitchToMainThreadAsync() directly).
  • Convert your code to run via Fire-and-Forget style tasks using JTF.RunAsync() from non-async methods.
  • Suppress these analyzer warnings. Please only do this as a carefully considered last resort, as they are intended to mitigate common conditions that create performance issues or hangs in VS.

Speaking of hangs in VS, in the EventOptionsDialog method in your example code, you're using System.Threading.Tasks.Task.Run(). This can also be dangerous in VS (or presumably any other app with UI thread concerns), as Task.Run does not support re-entrancy. For example, if something on the UI thread awaits Task.Run (which you didn't await, but for the sake of example), that will block the UI thread and if the delegated work tries to schedule more work back on the UI thread, it will deadlock and hang VS. Using JTF will mitigate this issue, as it does track re-entrant scenarios to some degree.

Joannjoanna answered 21/8, 2019 at 21:39 Comment(1)
I added an update in response to your answer. Basically, this technique throws a VSTHRD102 message. Code sample above.Memoir

© 2022 - 2024 — McMap. All rights reserved.