Awaitable (async) callbacks always execute in a pool thread, never the main thread.
The main thread
Hello!
Let's take this method (it doesn't return anything, but it's for an example):
private void GetProjects()
{
Guid emptyGuid=Guid.Empty;
IEnumHierarchies enumHierarchies;
int comResult=vsSolution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_ALLPROJECTS,ref emptyGuid,out enumHierarchies);
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(comResult);
}
Visual Studio swears at the GetProjectEnum method. There are two solutions here (in addition to disabling the analyzer). For a synchronous method, add this:
ThreadHelper.ThrowIfNotOnUIThread();
And for an asynchronous method add this:
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
And then I have a question. If I take events from Visual Studio. From things like EnvDTE.DTE (Events.SolutionEvents.ProjectAdded) or IVsSolutionEvents. And any like that. Is there a guarantee that they will always be called in the main thread? And then I'm writing this type now:
void IEvents.OnOpened(IVsHierarchy vsHierarchy)
{
ThreadHelper.JoinableTaskFactory.Run(async delegate
{
await (this as IEvents).OnOpened(vsHierarchy);
});
}
async Task IEvents.OnOpened(IVsHierarchy vsHierarchy)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
if(await Projects.Project.CheckIsCppProjectAsync(vsHierarchy))
{
Guid guid;
int comResult=vsHierarchy.GetGuidProperty((uint)VSConstants.VSITEMID.Root,(int)__VSHPROPID.VSHPROPID_ProjectIDGuid,out guid);
if(comResult!=VSConstants.S_OK) throw new Errors.ErrorCOM(typeof(IVsHierarchy),"GetGuidProperty",comResult);
Projects.Project project=new Projects.Project(baseInterfaces,vsHierarchy);
lock(syncProjects) projects.Add(guid,project);
await project.InitAsync();
}
}
Or maybe it's all in vain and here the code will always be executed in the main thread.
Also, if I take this code:
private async Task GetProjects1Async()
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
Guid emptyGuid=Guid.Empty;
IEnumHierarchies enumHierarchies;
int comResult=vsSolution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_ALLPROJECTS,ref emptyGuid,out enumHierarchies);
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(comResult);
GetProjects2();
await GetProjects3Async();
}
private void GetProjects2()
{
Guid emptyGuid=Guid.Empty;
IEnumHierarchies enumHierarchies;
int comResult=vsSolution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_ALLPROJECTS,ref emptyGuid,out enumHierarchies);
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(comResult);
}
private async Task GetProjects3Async()
{
Guid emptyGuid=Guid.Empty;
IEnumHierarchies enumHierarchies;
int comResult=vsSolution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_ALLPROJECTS,ref emptyGuid,out enumHierarchies);
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(comResult);
await DoSomething();
}
Visual Stidio will still swear at GetProjectEnum in the GetProjects2 and GetProjects3Async methods. But this is strange, because they are called in the GetProjects1Async method, which will definitely be executed on the main thread. What's the best thing to do here? Otherwise, it seems like a bad practice to add a ton of meaningless code, in the form of ThreadHelper.ThrowIfNotOnUIThread() and ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync().
2 answers
Sort by: Most helpful
-
-
Artemiy Moroz 271 Reputation points
2022-02-17T15:31:17.167+00:00 Besides that I am not a Visual Studio plugins specialist, I have some experience in writing multi-threaded code. If the documentation is obscure on this matter in visual studio (which is often happens for so narrow topic), or you are not sure on what thread does the function gets called, I would suggest using some debugging techniques like attaching a debugger, logging the current thread Id to the console, or even using ILSpy for looking into MS libraries.