Threads and apartments¶
WinRT objects can be used from more than one Python thread, but WinRT ties some things to a thread. This page says what that means for a program.
Apartments¶
WinRT runs every thread in a COM apartment. A program without a user interface needs to do nothing about it. A thread that has not chosen an apartment uses WinRT in the process's multithreaded apartment, which the runtime joins the first time it activates a class.
A thread that runs a user interface is a single-threaded apartment instead. Call
init_apartment() on it before it creates any
of the interface:
from winrt.runtime import ApartmentType, init_apartment
init_apartment(ApartmentType.SINGLE_THREADED)
The WinUI 2, WinUI 3 and WebView2 samples do this before they create their window.
What a single-threaded apartment changes¶
The blocking get() and wait() of an async
operation raise RuntimeError on such a thread, since blocking it would freeze
the interface. Await the operation instead, or give it a completed handler.
A XAML element belongs to the thread that created it and is used on that
thread. A call to it from any other thread fails with OSError and
RPC_E_WRONG_THREAD.
Packages that choose the apartment on import¶
Some Python packages for COM make the thread that first imports them a
single-threaded apartment. pywin32's pythoncom, which win32com imports, and
comtypes both do. A program that imports one of them, even through another
package, gets the changes above on that thread, so a get() that worked before
raises RuntimeError.
Both packages read sys.coinit_flags when they are imported. Setting it to
0, which is COINIT_MULTITHREADED, before the first import leaves the thread
in the multithreaded apartment:
Which thread runs your code¶
A handler that WinRT calls, for an event or for an operation's completed or
progress, runs on whatever thread WinRT calls it from, which is often not one
of the program's own.
Which thread calls the handler says
how to hand the work to the thread or event loop that should do it.
Such a thread keeps what Python stores per thread, such as a threading.local
or the context of contextvars, from one call to the next for as long as it
lives, as a thread of the program's own does. A thread pool thread serves the
callbacks of unrelated objects, though, so what one handler leaves there is
what the next handler on that thread finds.
Free-threaded Python¶
PyWinRT runs on the free-threaded build of CPython, where threads run Python
code at the same time. Import the WinRT modules a program uses before it starts
threads, or from one thread, as most programs do at the top of a module anyway.
Two threads importing WinRT modules that need each other for the first time at
once can fail with an import deadlock error, as with any Python package whose
modules import each other. Array says what it
guarantees when several threads use one.