Repository: nonebot/nonebot2

Issue:
Feature: 正向驱动器 startup/shutdown hook 同步函数支持
**描述问题：**

BlockDriver注册同步on_startup与on_shutdown钩子函数时启动崩溃

**如何复现？**

```python
from nonebot import get_driver


@get_driver().on_startup
def i_will_crash():
    print("oops")

```

**期望的结果**



**环境信息：**

 - OS: Windows 10
 - Python Version: 3.10
 - Nonebot Version: 2.0.0b4

**协议端信息：**

 - 协议端: Kaiheila
 - 协议端版本: 0.0.2

**截图或日志**
```
07-12 23:56:09 [SUCCESS] nonebot | NoneBot is initializing...
07-12 23:56:09 [INFO] nonebot | Current Env: dev
07-12 23:56:09 [DEBUG] nonebot | Loaded Config: {'driver': '~httpx+~websockets', 'host': IPv4Address('127.0.0.1'), 'port': 8080, 'log_level': 'DEBUG', 'api_timeout': 30.0, 'superusers': set(), 'nickname': set(), 'command_start': {'/'}, 'command_sep': {'.'}, 'session_expire_timeout': datetime.timedelta(seconds=120)}
07-12 23:56:09 [DEBUG] nonebot | Succeeded to load adapter "Kaiheila"
07-12 23:56:09 [SUCCESS] nonebot | Succeeded to import "echo"
07-12 23:56:09 [SUCCESS] nonebot | Succeeded to import "nonebot_plugin_pixivbot_kook"
07-12 23:56:09 [SUCCESS] nonebot | Succeeded to import "test"
07-12 23:56:09 [WARNING] __main__ | Always use `nb run` to start the bot instead of manually running!
07-12 23:56:09 [SUCCESS] nonebot | Running NoneBot...
07-12 23:56:09 [DEBUG] nonebot | Loaded adapters: Kaiheila
oops
07-12 23:56:09 [ERROR] nonebot | Error when running startup function. Ignored!
Traceback (most recent call last):
  File "C:\Users\huang\PycharmProjects\kuraku-kook\bot.py", line 21, in <module>
    nonebot.run()
  File "C:\Users\huang\PycharmProjects\kuraku-kook\venv\lib\site-packages\nonebot\__init__.py", line 261, in run
    get_driver().run(*args, **kwargs)
  File "C:\Users\huang\PycharmProjects\kuraku-kook\venv\lib\site-packages\nonebot\drivers\_block_driver.py", line 60, in run
    loop.run_until_complete(self.serve())
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 628, in run_until_complete
    self.run_forever()
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\windows_events.py", line 321, in run_forever
    super().run_forever()
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 595, in run_forever
    self._run_once()
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\base_events.py", line 1881, in _run_once
    handle._run()
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\Users\huang\PycharmProjects\kuraku-kook\venv\lib\site-packages\nonebot\drivers\_block_driver.py", line 64, in serve
    await self.startup()
> File "C:\Users\huang\PycharmProjects\kuraku-kook\venv\lib\site-packages\nonebot\drivers\_block_driver.py", line 75, in startup
    await asyncio.gather(*cors)
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 776, in gather
    fut = _ensure_future(arg, loop=loop)
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 629, in _ensure_future
    raise TypeError('An asyncio.Future, a coroutine or an awaitable '
TypeError: An asyncio.Future, a coroutine or an awaitable is required
07-12 23:56:09 [INFO] nonebot | Application startup completed.
Exception in callback gather.<locals>._done_callback(<Task finishe...> result=None>) at C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py:714
handle: <Handle gather.<locals>._done_callback(<Task finishe...> result=None>) at C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py:714>
Traceback (most recent call last):
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\Users\huang\AppData\Local\Programs\Python\Python310\lib\asyncio\tasks.py", line 718, in _done_callback
    if outer.done():
NameError: free variable 'outer' referenced before assignment in enclosing scope
07-12 23:56:09 [DEBUG] nonebot | Kaiheila | WebSocket Connection to wss://ws.kaiheila.cn/gateway?compress=0&token=***************** established
07-12 23:56:09 [INFO] nonebot | Kaiheila | Bot ******** connected
07-12 23:56:09 [DEBUG] nonebot | Kaiheila | Bot ******** HeartBeat
07-12 23:56:35 [DEBUG] nonebot | Kaiheila | Bot ******** HeartBeat
07-12 23:57:01 [DEBUG] nonebot | Kaiheila | Bot ******** HeartBeat
```



Discussion:
应该是这里处理钩子函数的时候同时有异步和非异步时，coros内有None元素导致await asyncio.gather(*cors)抛出异常
![QQ截图20220713000201](https://user-images.githubusercontent.com/17331698/178539455-eb6fe196-9a82-4941-b048-fca9c7676089.png)

block driver不支持同步hook，类型都写在on_startup和shutdown函数里了
可以考虑增加一下同步支持

Resolving patch:
diff --git a/nonebot/drivers/_block_driver.py b/nonebot/drivers/_block_driver.py
--- a/nonebot/drivers/_block_driver.py
+++ b/nonebot/drivers/_block_driver.py
@@ -1,15 +1,16 @@
 import signal
 import asyncio
 import threading
-from typing import Set, Callable, Awaitable
+from typing import Set, Union, Callable, Awaitable
 
 from nonebot.log import logger
 from nonebot.drivers import Driver
 from nonebot.typing import overrides
 from nonebot.config import Env, Config
+from nonebot.utils import run_sync, is_coroutine_callable
 
-STARTUP_FUNC = Callable[[], Awaitable[None]]
-SHUTDOWN_FUNC = Callable[[], Awaitable[None]]
+STARTUP_FUNC = Callable[[], Union[None, Awaitable[None]]]
+SHUTDOWN_FUNC = Callable[[], Union[None, Awaitable[None]]]
 HANDLED_SIGNALS = (
     signal.SIGINT,  # Unix signal 2. Sent by Ctrl+C.
     signal.SIGTERM,  # Unix signal 15. Sent by `kill <pid>`.
@@ -69,7 +70,10 @@ async def serve(self):
 
     async def startup(self):
         # run startup
-        cors = [startup() for startup in self.startup_funcs]
+        cors = [
+            startup() if is_coroutine_callable(startup) else run_sync(startup)()
+            for startup in self.startup_funcs
+        ]
         if cors:
             try:
                 await asyncio.gather(*cors)
@@ -89,7 +93,10 @@ async def shutdown(self):
 
         logger.info("Waiting for application shutdown.")
         # run shutdown
-        cors = [shutdown() for shutdown in self.shutdown_funcs]
+        cors = [
+            shutdown() if is_coroutine_callable(shutdown) else run_sync(shutdown)()
+            for shutdown in self.shutdown_funcs
+        ]
         if cors:
             try:
                 await asyncio.gather(*cors)


Test patch: