microsoft / VSExtensibility

A repo for upcoming changes to extensibility in Visual Studio, the new extensibility model, and language server protocol.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Hang after ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()

LeeMSilver opened this issue · comments

in-proc - use Experimental Instance (EI)

I have isolated all my calls that need the method in the title into one method:

   static async Task ExecuteOnMainThreadA(String? wDTECommand, String? wStatusBarText, String? wStatusBar)
      {
      await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

      if (wDTECommand?.Length > 0)
         {
         MyDTE.ExecuteCommand(wDTECommand);
         }
      else if (wStatusBarText?.Length > 0)
         {
         yStatusBar.Text = wStatusBarText;
         }
      else
         {
         yStatusBar = MyDTE.StatusBar;
         }

      await TaskScheduler.Default.SwitchTo();
      }

I'm sure I'm something wrong, but I think I've followed all the advice/instructions I've been given on switching to the Main thread.

The statement being executed in this call: yStatusBar = MyDTE.StatusBar; executes correctly and yStatusBar is correctly populated.
The problem is as soon as the above method exits (using F11`) the EI's UI is displayed - the command does not finish executing.

I tried changing the last line to await TaskScheduler.Default; and removing it completely, all with the same results.

If I remove both await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); and await TaskScheduler.Default.SwitchTo(); it runs successfully; but this is a work-around because 1) the MyDTE.ExecuteCommand(wDTECommand); modifies the source-file and I'm pretty sure needs to run on the Main thread and 2) the yStatusBar.Text = wStatusBarText; probably also needs the Main thread. I'm not ready for testing these 2 paths yet; but if this issue isn't resolved when I do test them I'll post the results here.

Per the example https://github.com/Microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD010.md, the last line in the above method has been changed to
await TaskScheduler.Default;
The comments following the posted code remain true after the above change.

I have tried modeling my code after that found in the CommentRemover sample.

My sequence of calls is (noting that I deleted some simple assignments for brevity)

public static async Task ClassInitializeA()
   {
   await SetStatusBarA();
   }
static async Task SetStatusBarA() => await ExecuteOnMainThreadA(null, null, "X");
static async Task ExecuteOnMainThreadA(String? wDTECommand, String? wStatusBarText, String? wStatusBar)
   {
   await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
   
   if (wDTECommand?.Length > 0)
      {
      }
   else if (wStatusBarText?.Length > 0)
      {
      }
   else
      {
      yStatusBar = CmdDTE.StatusBar;
      }
   }

When I F11 starting at SetStatusBarA(); in ClassInitializeA, SetStatusBarA() is jumped to followed by ExecuteOnMainThreadA() as expected. When the latter finished F11 jumps back to SetStatusBarA(); in ClassInitializeA, then to the following } and then back to the UI -- not to the caller of ClassInitializeA. Clicking the Debug-menu in the main VS window Continue is disabled and my only choices are Break All and Stop Debugging. When I click Stop Debugging the text at the top says "Your app has entered a break state but no code is running that is supported by the selected debug engine (e.g. only native runtime code is executing.)"

I also tried adding await TaskScheduler.Default; as the last statement in the above method with the same results I describe bove. I did this because most of the commands that call the above method have to continue execution on the UI thread and will call the above again later in their execution (e.g. to run CodeCleanup.) If this is not possible I will not be able to convert my extension to the new extensibility model until it is supported or it has native support (without calling await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();) for DTE.ExecuteCommand().

I know I'm doing something wrong, but I don't know what. It would be useful if someone could point me to running code that calls await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); and await TaskScheduler.Default; or a snippet of running code that does the same.