If you want to set a thread’s apartment model via Thread.CurrentThread.ApartmentState, you need to act quickly

Welcome to CLR Week 2014. Don't worry, it'll be over in a few days.

A customer wanted to know why their Folder­Browser­Dialog was displaying the infamous Current thread must be set to single thread apartment (STA) mode before OLE calls can be made error.

private void btnBrowseFolder_Click(object sender, System.EventArgs e)
  Thread.CurrentThread.ApartmentState = ApartmentState.STA;
  FolderBrowserDialog fbd = new FolderBrowserDialog {
    RootFolder = System.Environment.SpecialFolder.MyComputer,
    ShowNewFolderButton = true,
    Description = "Select the awesome folder..."
  DialogResult dr = fbd.ShowDialog();

"Even though we set the Apartment­State to STA, the apartment state is still MTA. Curiously, if we put the above code in a standalone test program, it works fine."

The problem is that the customer is changing the apartment state too late.

On the first call to unmanaged code, the runtime calls Co­Initialize­Ex to initialize the COM apartment as either an MTA or an STA apartment. You can control the type of apartment created by setting the System.Threading.ApartmentState property on the thread to MTA, STA, or Unknown.

Notice that the value you specify in Current­Thread.Apartment­State is consulted at the point the runtime initializes the COM apartment (which occurs on the first call to unmanaged code). If you change it after the COM apartment has been initialized, you're revising the blueprints of a house after it has been built.

The standard way to avoid this problem is to attach the [STAThread] attribute to your Main function, or if you need to set the apartment model of a thread you created yourself, call the Thread.Set­Apartment­State method before the thread starts.

Comments (18)
  1. Joshua says:

    I wonder if a universal wrapper could be made so that anybody requesting an STA on an MTA or free thread can get the wrapper and work. Every example I've seen boiled down to calling DialogBox in an MTA thread which seems like it could be safe.

  2. skSdnW says:

    This could/should be fixed in SHBrowseForFolder. It should be pretty simple: if (!IsCurrentThreadSTA()) { SHCreateThreadWithHandle(CTF_COINIT_STA, DoSHBrowseForFolder, pbi); WaitForSingleObject(…); return ..; } else return DoSHBrowseForFolder(pbi);

  3. Joshua says:

    Try MsgWaitFor …

  4. Anon says:

    What's with the "…the infamous… error…" construction? I see it constantly, but most of the time it is shorthand for "This error is scary because I can't spend two minutes looking it up."

    See also:

    "…the infamous bluescreen error…"

    "…the infamous stop error…"

    "…the infamous File Not Found error…"

  5. Gearov says:

    Anon: It's just a snowclone. The infamous … error construct is the new black.

    [There is also "The infamous … message", "The infamous … letter", and so on. It's not even a snowclone. It's just an adjective. -Raymond]
  6. John Doe says:

    @Anon, after the fact, and if anyone on the interwebs ever cares to publish an analysis and fix or workaround, you might spend at most two minutes looking the error up, and it might be the same error, and it might describe the specific cause or a series of causes, and one of the suggestions might actually work (you still have to try them out).

    Or not, for each might.  Alas, these are the infamous errors where the description might not help.  If you know COM, this description will make perfect sense.  If you don't, you may ask "WTH is an apartment, aren't domains enough?", Of Course™, one thing having little or nothing to do with the other.

    There went two minutes, who knows how many more…

  7. Aron Parker says:

    Woohoo! I thought those CLR weeks were extinct, can't wait to see more! I love this blog!

  8. Gabe says:

    skSdnW: Why don't you try that and let us know how it works?

  9. T says:

    "On the first call to unmanaged code, the runtime calls Co­Initialize­Ex to initialize the COM apartment as either an MTA or an STA apartment."

    Does it do it even if you don't deal with COM at all and call native code through a P/Invoke?

  10. skSdnW says:

    @Gabe: How do you suggest I try that? Injecting into every process and hook SHBrowseForFolder?

    Only MS could fix it but it is not hard: http://pastebin.com/ebGi4RGQ

    [Try changing a system setting while the dialog box is up. Or try doing anything with thread affinity in the browse dialog callback. -Raymond]
  11. "Welcome to CLR Week 2014. Don't worry, it'll be over in a few days."

    I am excited and don't want it be over… ever.

  12. skSdnW says:

    @Raymond: I don't know what kind of system setting you are talking about. As far as thread affinity goes, MSDN does not say anything about which thread the callback is called from but most people probably assume it is the same thread. If a MTA workaround was added it could just say that it is undefined which thread the callback is called from when the caller was MTA.

    [Anything that triggers a broadcast. And you yourself noted that most people assume that it is the same thread. Adding an MTA workaround would therefore break people who made that assumption. And all this to work around something that is against best practice anyway (doing UI from an MTA thread). -Raymond]
  13. Joshua says:

    [Try changing a system setting while the dialog box is up.]

    My point about the Msg version.

    [Or try doing anything with thread affinity in the browse dialog callback. -Raymond]

    I could fix that in half a day by invoking the call back to the initiating thread. I've written similar things before. To be fair I didn't know this dialog had a callback until just now. I've never needed it.

    [(doing UI from an MTA thread). -Raymond]

    If it's an MTA client (as opposed to server) thread, there should be no problem with it. Artificiality creating a thread to satisfy an assertion is silly.

    [You can't marshal the call back to the initiating thread, because it will try to interact with the UI elements from the wrong thread. -Raymond]
  14. Joshua says:

    OK so that fix doesn't work. Yet the lack of a fix seems unreasonable. Being able to create, use, and finally discard single threaded objects within the lifetime of a single message to a multi-threaded server should be the NORMAL case not the strange difficult case.

    [(1) That single-threaded object may in turn create other single-threaded objects. And how can you prove that the object is discarded before the call returns? Maybe somebody passed a reference to a background thread, or cached it in a thread-local object. (2) Doing UI from an MTA thread is explicitly a bad idea. We shouldn't be making it easier to carry through on bad ideas. That's the pit of failure. -Raymond]
  15. cheong00 says:

    Btw, since the [STAThread] is automatically added to Program.cs when the project is created, it's kind of puzzling on how it got lost on the way.

  16. skSdnW says:

    @Raymond: MSDN says this about MTA callers: "… SHBrowseForFolder fails if the calling application uses the BIF_USENEWUI or BIF_NEWDIALOGSTYLE flag…" so how would this break existing code? Or you could add a new BIF_* flag for MTA callers. Win32 is dead so it is not going to happen but it would have been nice 10 years ago.

    Basically, any API that shows a dialog can get this MTA issue in the future. Imagine this; the dialog created by ChooseFont() gets new buttons to save and load custom colors or to load a color from a palette file. You would end up calling GetOpenFileName which hosts shell extensions that expect to be called from a STA thread…

    [How would changing behavior break existing code? Because you're changing behavior. There would need to be a new BIF_* flag, but if you need to change your code to get the altered behavior, you may as well change your code to do the helper thread. So there's no real value added aside from convenience. -Raymond]
  17. Xv8 says:

    @cheong00: People who don't use the project templates?

    The C++ template annoys the hell out of me, I imagine there must be some people who don't use the C# template too.

  18. nobugz says:

    The code is fake.  As behooves managed code, you get an InvalidOperationException when you try to change the apartment state too late.  Exception message "Failed to set the specified COM apartment state".  Try it.

Comments are closed.