From 3a7ec69f2e84bd1cb7075898b762d75330ba7432 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 7 May 2024 15:07:41 -0500 Subject: [PATCH] backends.winrt: add allow_sta() utility function We had lots of users reporting issues with the WinRT backend hanging forever when trying to connect to a device. This was happening because some other imported library was initializing the the main thread to STA which caused some async callback to never be called. To work around this, we added a check to make sure the main thread is set to MTA rather than STA. Unfortunately, in cases where there is a graphical user interface library being used AND that library is properly interated with asynco so that Bleak runs in the main thread rather than in a background thread, the thread type does need to be STA for the GUI to work. So in those very specific conditions, we need to not raise an exception. We don't know of a way to detect this automatically, so we added a new utility function `allow_sta()` that the user can call to allow Bleak to run in an STA thread. --- CHANGELOG.rst | 4 ++++ bleak/backends/winrt/util.py | 20 ++++++++++++++++++++ docs/troubleshooting.rst | 21 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 01c44dc5..5221af84 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,10 @@ and this project adheres to `Semantic Versioning None: .. versionadded:: 0.22 """ + if hasattr(allow_sta, "_allowed"): + return + try: apt_type, _ = _get_apartment_type() if apt_type != _AptType.MTA: @@ -80,6 +83,23 @@ def assert_mta() -> None: if e.winerror != _CO_E_NOTINITIALIZED: raise +def allow_sta(): + """ + Suppress check for MTA thread type and allow STA. + + Bleak will hang forever if the current thread is not MTA - unless there is + a Windows event loop running that is properly integrated with asyncio in + Python. + + If your program meets that condition, you must call this function do disable + the check for MTA. If your program doesn't have a graphical user interface + you probably shouldn't call this function. and use ``uninitialize_sta()`` + instead. + + .. versionadded:: unreleased + """ + allow_sta._allowed = True + def uninitialize_sta(): """ diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index e299d18f..2fbd5d5d 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -82,8 +82,25 @@ Bleak should detect this and raise an exception with a message similar to:: The current thread apartment type is not MTA: STA. -To work around this, you can use a utility function provided by Bleak to -uninitialize the threading model after importing an offending package:: +To work around this, you can use one of the utility functions provided by Bleak. + +If your program has a graphical user interface and the UI framework *and* it is +properly integrated with asyncio *and* Bleak is not running on a background +thread then call ``allow_sta()`` before calling any other Bleak APis:: + + try: + from bleak.backends.winrt.util import allow_sta + # tell Bleak we are using a graphical user interface that has been properly + # configured to work with asyncio + allow_sta() + except ImportError: + # other OSes and older versions of Bleak will raise ImportError which we + # can safely ignore + pass + +The more typical case, though, is that some library has imported something like +``pywin32`` which breaks Bleak. In this case, you can uninitialize the threading +model like this:: import win32com # this sets current thread to STA :-( from bleak.backends.winrt.utils import uninitialize_sta