Repository: agronholm/anyio

Issue:
Event.set() with an `EventAdapter` does not work unless already running in an async event loop
### Things to check first

- [X] I have searched the existing issues and didn't find my bug already reported there

- [X] I have checked that my bug is still present in the latest release


### AnyIO version

4.6.2

### Python version

3.12

### What happened?

Unexpected exception.  Code that runs fine with trio and asyncio fails with anyio.

### How can we reproduce the bug?

In a newly started Python:
```
from anyio import Event
event = Event()
event.set()
```
This code works fine if we replace `anyio` with `trio` or `asyncio`.  On `anyio` I get the error message `AsyncLibraryNotFound`.  

It seems strange that `anyio` has no problem creating the `Event`, but fails in setting it.  


Discussion:

Patch:
diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst
index 1485a7d..e8bef59 100644
--- a/docs/versionhistory.rst
+++ b/docs/versionhistory.rst
@@ -13,6 +13,9 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
   an object with a ``.fileno()`` method or an integer handle, and deprecated
   their now obsolete versions (``wait_socket_readable()`` and
   ``wait_socket_writable()``) (PR by @davidbrochart)
+- Changed ``EventAdapter`` (an ``Event`` with no bound async backend) to allow ``set()``
+  to work even before an async backend is bound to it
+  (`#819 <https://github.com/agronholm/anyio/issues/819>`_)
 - Added support for ``wait_readable()`` and ``wait_writable()`` on ``ProactorEventLoop``
   (used on asyncio + Windows by default)
 - Fixed the return type annotations of ``readinto()`` and ``readinto1()`` methods in the
diff --git a/src/anyio/_core/_synchronization.py b/src/anyio/_core/_synchronization.py
index 023ab73..7878ba6 100644
--- a/src/anyio/_core/_synchronization.py
+++ b/src/anyio/_core/_synchronization.py
@@ -109,6 +109,7 @@ class Event:
 
 class EventAdapter(Event):
     _internal_event: Event | None = None
+    _is_set: bool = False
 
     def __new__(cls) -> EventAdapter:
         return object.__new__(cls)
@@ -117,14 +118,22 @@ class EventAdapter(Event):
     def _event(self) -> Event:
         if self._internal_event is None:
             self._internal_event = get_async_backend().create_event()
+            if self._is_set:
+                self._internal_event.set()
 
         return self._internal_event
 
     def set(self) -> None:
-        self._event.set()
+        if self._internal_event is None:
+            self._is_set = True
+        else:
+            self._event.set()
 
     def is_set(self) -> bool:
-        return self._internal_event is not None and self._internal_event.is_set()
+        if self._internal_event is None:
+            return self._is_set
+
+        return self._internal_event.is_set()
 
     async def wait(self) -> None:
         await self._event.wait()


Test patch:
diff --git a/tests/test_synchronization.py b/tests/test_synchronization.py
index cc58188..83758c6 100644
--- a/tests/test_synchronization.py
+++ b/tests/test_synchronization.py
@@ -316,16 +316,17 @@ class TestEvent:
     def test_instantiate_outside_event_loop(
         self, anyio_backend_name: str, anyio_backend_options: dict[str, Any]
     ) -> None:
-        async def use_event() -> None:
-            event.set()
-            await event.wait()
-
         event = Event()
         assert not event.is_set()
         assert event.statistics().tasks_waiting == 0
 
+        event.set()
+        assert event.is_set()
+
         run(
-            use_event, backend=anyio_backend_name, backend_options=anyio_backend_options
+            event.wait,
+            backend=anyio_backend_name,
+            backend_options=anyio_backend_options,
         )