Skip to content
Back to skills

Cross Thread Async

ASecurity

Debug asyncio callbacks from non-async threads GUI or mDNS

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 10, 2026
ai-agentspythondebuggingapi

Works with

  • api

Security analysis

A100/100

Scanned September 10, 2026

npx -y skills add LoopyLuci/Skills --skill cross-thread-async --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cross Thread Async?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cross Thread Async
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/loopyluci-cross-thread-async/badge)](https://www.skillsdirectory.com/skills/loopyluci-cross-thread-async)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cross-thread-async
description: "Debug asyncio callbacks from non-async threads GUI or mDNS"
---

# Cross-Thread Async Patterns

Handle asyncio callbacks that fire from **non-async threads** — GUI event loops, mDNS service browsers, hardware polling, or thread-pool workers.

## The Problem

When `asyncio.get_event_loop()` is called from a thread that wasn't started by `asyncio.run()`, Python 3.10+ raises:

```
RuntimeError: There is no current event loop in thread 'zeroconf-ServiceBrowser-...'
```

Because `asyncio.run()` installs the loop only on the calling thread. Sub-threads do not inherit it.

## The Fix: Store the Loop Reference

```python
class ServiceWithCallbacks:
    def __init__(self):
        self._loop = None  # Set externally

    def set_event_loop(self, loop):
        self._loop = loop

    def _on_service_state_change(self):
        # WRONG — fails in non-async threads:
        # asyncio.get_event_loop()  → RuntimeError

        # RIGHT:
        asyncio.run_coroutine_threadsafe(
            self._on_device_added(),
            self._loop  # stored reference
        )
```

**Wire at startup:**
```python
loop = asyncio.get_running_loop()
service.set_event_loop(loop)
```

## When This Pattern Applies

| Scenario | Library | Thread Source |
|----------|---------|--------------|
| mDNS discovery | `zeroconf` | ServiceBrowser background thread |
| GUI toolkit | `tkinter`, `PyQt`, `CustomTkinter` | Main thread ≠ async loop thread |
| File watcher | `watchdog` | Observer thread |
| Hardware events | `pyserial`, `bluetooth` | Reader threads |
| Thread pool | `concurrent.futures` | Worker threads |

## Alternative: Dedicated Async Thread

When ALL async work lives in a background thread:

```python
def background_worker():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_forever()

thread = threading.Thread(target=background_worker, daemon=True)
thread.start()
# Now get_event_loop() works inside this thread
```

Use this for pure async-daemon patterns. Don't use it if the main thread also needs async.

## Detecting the Bug Early

```python
# Add a guard at API boundaries
import asyncio

def schedule_coro(coro):
    try:
        loop = asyncio.get_running_loop()
        # We're in an async context — schedule directly
        asyncio.ensure_future(coro)
    except RuntimeError:
        # No running loop — must have stored reference
        raise RuntimeError(
            "Called from non-async thread without stored loop reference"
        )
```

## Source of the Pattern

Discovered while debugging the InstantTransfer project:
- `zeroconf.ServiceBrowser` fires `ServiceStateChange` callbacks from a background thread
- Calling `asyncio.get_event_loop()` there raises `RuntimeError`
- Fix: store `self._loop = asyncio.get_running_loop()` in the async init, use `asyncio.run_coroutine_threadsafe(coro, self._loop)` in callbacks

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.