Do i need to call CoInitialize before interacting

2020-02-23 08:14发布

问题:

i know that a requirement of COM that every thread call CoInitialize before interacting with the COM system.

.NET exposes some items that internally operate on threads, e.g.:

  • ThreadPool threads
  • asychronous delegates (which use thread pool threads)
  • BackgroundWorker class (which use asychronous delegates (which use thread pool threads))
  • the garbage collector
  • and more! (i.e. e.g.)

If i am going to be interacting with a COM object from a thread, do i need to call CoInitialize first?

i ask because there may be some more magic that automagically calls it for me - i don't know.


Bonus Reading

Managed and Unmanaged Threading

For interoperability, the common language runtime creates and initializes an apartment when calling a COM object. A managed thread can create and enter a single-threaded apartment (STA) that contains only one thread, or a multi-threaded apartment (MTA) that contains one or more threads. When a COM apartment and a thread-generated apartment are compatible, COM allows the calling thread to make calls directly to the COM object. If the apartments are incompatible, COM creates a compatible apartment and marshals all calls through a proxy in the new apartment.

The runtime calls CoInitializeEx to initialize the COM apartment as either an MTA or an STA apartment.

Update Two:

Looks like you should not use COM from any kind of thread that .NET can provide:

The Managed Thread Pool

There are several scenarios in which it is appropriate to create and manage your own threads instead of using thread pool threads:

  • You require a foreground thread.

  • You require a thread to have a particular priority.

  • You have tasks that cause the thread to block for long periods of time. The thread pool has a maximum number of threads, so a large number of blocked thread pool threads might prevent tasks from starting.

  • You need to place threads into a single-threaded apartment. All ThreadPool threads are in the multithreaded apartment.

  • You need to have a stable identity associated with the thread, or to dedicate a thread to a task.

Update Three:

Looks like you can set the threading model of unmanged threads:

Managed and Unmanaged Threading in Microsoft Windows

A managed thread can be marked to indicate that it will host a single-threaded or multithreaded apartment. The GetApartmentState, SetApartmentState, and TrySetApartmentState methods of the Thread class return and assign the apartment state of a thread. If the state has not been set, GetApartmentState returns ApartmentState.Unknown.

The property can be set only when the thread is in the ThreadState.Unstarted state; it can be set only once for a thread.

If the apartment state is not set before the thread is started, the thread is initialized as a multithreaded apartment (MTA).

Lot of conflicting information.

Which is why we'll use whatever the guy on Stackoverflow said as the true answer.

回答1:

The information here isn't actually conflicting - it's just not necessarily super clear if you're new to COM.

Short answer:

  • .Net threads are always already CoInitialized for you - you don't have to (and should not!) call it yourself.
  • ThreadPool threads (and therefore anything that uses ThreadPool threads, like asynchronous delegates and so on) are always initialized MTA. The only option for creating an STA thread is either adding the [STAThread] attribute to Main() to request that the runtime initializes the main thread as STA, or using thread.SetApartmentState(ApartmentState.STA) on a new thread you create before calling thread.Start() - otherwise they are MTA by default. In any case, the thread apartment model cannot be modified once the thread is started and running.

Longer answer: there are two ways to call CoInitialize - you can use it to initialize your thread as a Single-Threaded Apartment thread (STA), or as a Multi-Threaded Apartment thread (MTA). What the text above is saying is that by default, new threads and threadpool threads are automatically pre-CoInitialized as MTA-flavor. But with a new thread, you can use ApartmentState to specifiy STA-flavor, if you do so before actually starting the thread. It's always CoInitialized one way or the other by the time it's started anyhow.

Note that Main() on UI-based programs is marked with the [STAThread] attribute to ensure that it's STA-based; while on a console app, lack of [STAThread] means it's CoInited as MTA. The reason for this attribute, by the way, is that the thread that calls Main() is the one thread you can't specify STA vs MTA using ApartmentState - because it's already running and by the time Main() executes, so too late to use that; so think of the attribute as a hint to the runtime to set the apartment state before Main() is called.

The key thing to be aware of is that STA is usually used with UI and requires a message loop (which .Net WinForms provides for you); STA code should never block with Sleep() or similar, else your UI will also block. MTA, on the other hand, is designed for worker usage - background tasks, downloading files or doing computations in the background, for example, and generally should not own UI. You can use COM from either of these, but it may depend on what the COM object is doing or where you got it from. If it's a UI component, likely you'd want to use it from a STA thread; on the other hand, if it's a component for downloading or doing computations, you'd typically use it from an MTA thread.

Update 1 above is basically saying that the .Net runtime always calls CoInitialize for you - but lets you chose STA vs MTA, with MTA being the default.

Update 2 above is basically saying that since ThreadPool threads are MTA (and you don't get to change that), you should only use them for doing background operations, and not use them for UI tasks.

Update 3 is saying that for new threads, you can chose MTA vs STA - same as update 1, just being more explicit about the APIs.

The whole MTA vs STA thing can get quite complex, suggest reading this article as a starting point. The big picture, though, is mostly summarized by remembering that STA = single thread and UI; MTA = multiple threads, background/worker tasks. (STA vs MTA also applies to objects, not just threads, and COM does a whole bunch of work behind the scenes to let the different types of threads use the different types of objects. When it works well, you don't realize it and can blissfully ignore it; but when you hit a restriction or limitation, it can often be tricky to figure out just what's going on.)



回答2:

To answer your first question, if I remember my Don Box correctly, every thread MUST call CoInitialize. No exceptions.

As to the automagical part, I have no idea.