Run filters in lock context
Currently, unless a filter implementation explicitly puts all non-`Session`-method async calls in a context in which `Session._broadcast` is locked the filter will suffer from severe race-conditions. If `Session`-method calls are in such a context, a `RuntimeError` will be raised as locks from `anyio` are not re-entrant.
The methods of `Session` in *session.py* need to drop all the `Broadcast` locking and assume that the task they are called in has already locked it. Filters must be run in a task with the lock already acquired.
<details><summary>Description of work</summary>
- [x] Remove the async context everywhere the following pattern occurs currently in *session.py*:
```python
async with self._broadcast:
...
```
- [x] Make `Session` usable as an async context which wraps the `Broadcast`
- [x] Update *runner.py* to use the `Session` as a context
</details>
issue