Link to home
Start Free TrialLog in
Avatar of KevinJoeBadger
KevinJoeBadgerFlag for United Kingdom of Great Britain and Northern Ireland

asked on

How can I avoid the need for "ThrowIfNotOnUIThread()" calls in my code?

Visual Studio 2017.

I am getting this message from the compiler:

Accessing "TextDocument" should only be done on the main thread. Call lMicrosoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread() first.

Could someone explain what I am doing wrong please?

I am not sure I understand what the "main thread" is in this case or where I should be accessing this code?

EnvDTE80.DTE2 DTE2 = Package.GetGlobalService(typeof(DTE)) as DTE2;
if (DTE2.ActiveDocument != null)
{
     TextDocument TheDocument = (TextDocument)DTE2.ActiveDocument.Object("TextDocument");
.
.
.

Open in new window




I can remove the warning by adding ThrowIfNotOnUIThread() but is this the real solution?
Avatar of Eduard Ghergu
Eduard Ghergu
Flag of Romania image

Hi,
Most likely, the problem is on how DTE2 is created.

From your main Package class you can use:
EnvDTE80.DTE2 dte = this.GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE)) as EnvDTE80.DTE2;

If you are not in the main package, you can use this:
EnvDTE.DTE dte = Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE)) as EnvDTE.DTE; (or use EnvDTE80.DTE2 if you need that instead.

https://stackoverflow.com/questions/19087186/how-to-acquire-dte-object-instance-in-a-vs-package-project
to add to above solution:

the main thread is the UI thread, i. e. when a c# program starts up, one thread begins running immediately. this is usually called the main thread of your program. if you invoke an asynchronously running task from user interface, then this is running in a different thread which is not the ui thread. each thread of a program has its unique thread context. the 'ActiveDocument' is bound to the context of the ui thread and therefore you get a message if you try to access the document from a different thread context. the way out was shown by Eduard. instead of creating a new DTE2 object in the thread, you should use the the global dte2 object to get rid of the message.

Sara
Avatar of KevinJoeBadger

ASKER

Thanks.
This does not solve the issue though.
Using "the global dte2 object", I am still accessing the actual document in the same thread.

I suppose I need to move this access to the main form and make calls to it there.
Yes, you'll need to move it to the main form that is running on the main thread.
when you create a task or thread you normally would pass an object created by main thread to the worker thread which then could be used to exchange data in both directions. that could be as simple as to set some sort of traffic lights to signal that some kind of information is ready to get fetched. for those it is important that only side could set these flags while the other would only read and reset the flags. after fetching the data, a further flag was set be the worker to signal that the exchange has finished. that way it is threadsafe. for your current issue the thread could claim for the dte2 object to be accessed by the main thread by addding this request to some kind of (shared) queue- the main thread would check the queue continuously for example by a timer and perform the requested operation in the context of the main thread. the results would be put into a shared result container (queue) whic is owned by the worker thread.  by firing an event or by setting a result-is-available-flag, the worker thread savely could read and clear the result data. all this also could be done by using the Async interface c# offers or by using events.

Sara
Hi,
Ok I sort-of follow this but I don't have access to the main thread?

My code is a VS extension. The "Execute" method is called when I select the  menu command generated for this:

 internal sealed class ClassWizard
{
.
.
.
        private void Execute(object sender, EventArgs e)
        {
            ThreadHelper.ThrowIfNotOnUIThread();
            System.Windows.Forms.Form ClassWizard = new ClassWizardImp((TextDocument)_DTE2.ActiveDocument.Object("TextDocument"));
            ClassWizard.Show();
        }
}

This is the entry point to my code.

If I remove "ThreadHelper.ThrowIfNotOnUIThread();" then I get the message about no accessing outside the main thread.

but I have no access to the main thread!

Is it ok to use this at this level?

or should I make a call to switch to the main thread?
Hi,

The code that you presented works?

ThreadHelper.ThrowIfNotOnUIThread(); is just a 'safety net' rearding the execution, following the 'fail fast' principle.
This question needs an answer!
Become an EE member today
7 DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform.
View membership options
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.