需要从同步实例方法调用异步实例方法

问题描述 投票:0回答:1

send_message() 从线程中调用。由于 my_channel.send() 是异步的,我认为有必要让同步 send_message() 调用异步 asend_message() 。怎么办?

#@typechecked
class MessageCog(Cog):

    @typechecked
    async def asend_message(self, message:str)->str:
        my_channel:TextChannel    = self.bot.get_channel(self.channel)
        assert isinstance(my_channel, TextChannel)
        embed     :Embed          = Embed(title="Response", description=message, color=pepe_green)
        assert isinstance(embed, Embed)
        my_message:Message        = await my_channel.send(embed=embed)
        assert isinstance(my_message, Message)
        result    :str            = str(my_message) # ??
        assert isinstance(result, str)
        if not isinstance(result, str): raise Exception()
        return result

    @typechecked
    def send_message(self, message:str)->str:
        #
        #f:Coroutine = lambda m: self.asend_message(m)
        #result:str = run(f)
        #
        #result:str = run(self.asend_message(message))
        #
        #result:str = get_running_loop().run_untilcomplete(self.asend_message(message))
        #
        #f:Callable[[str],str] = async_to_sync(MessageCog.asend_message)
        #
        #f = async_to_sync(MessageCog.asend_message)
        #result:str = f(self, message)
        #
        #result:str = create_task(self.asend_message(message))
        #
        #f = partial(MessageCog.asend_message, self)
        #result:str = run(f(message))
        assert isinstance(result, str)
        if not isinstance(result, str): raise Exception()
        return result

错误总是相同的:

[chain/error] [chain:AgentExecutor] [82.96s] Chain run errored with error:
...
TypeError: type of the return value must be str; got coroutine instead

更新:新错误!

我想我终于找到了正确的类型。 async 返回一个返回 str 的协程。

async def asend_message(self, message:str)->Coroutine[str]:

lambda 创建一个可调用函数,返回一个协程...

f:Callable[[str],Coroutine[str]] = lambda m: self.asend_message(m)
g:Coroutine[str] = f(message)
result:str = run(g)

看到新错误后,我的第一反应是责怪 langchain,但我认为堆栈跟踪表明我使用 asyncio 的方式存在问题:

[tool/error] [chain:AgentExecutor > tool:send_message] [3ms] Tool run errored with error:
RuntimeError('Timeout context manager should be used inside a task')Traceback (most recent call last):

  File "/home/user/.local/lib/python3.10/site-packages/langchain_core/tools.py", line 409, in run
    context.run(

  File "/home/user/.local/lib/python3.10/site-packages/langchain_core/tools.py", line 750, in _run
    else self.func(*args, **kwargs)

  File "/home/user/enumerate_bot/./eris.py", line 380, in <lambda>
    func       =lambda message: self. send_message(message),

  File "/usr/lib/python3/dist-packages/typeguard.py", line 494, in wrapper
    retval = func(*args, **kwargs)

  File "/home/user/enumerate_bot/./eris.py", line 335, in send_message
    result:str = run(g)

  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)

  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()

  File "/home/user/enumerate_bot/./eris.py", line 306, in asend_message
    my_message:Message        = await my_channel.send(embed=embed)

  File "/home/user/.local/lib/python3.10/site-packages/discord/abc.py", line 1618, in send
    data = await state.http.send_message(channel.id, params=params)

  File "/home/user/.local/lib/python3.10/site-packages/discord/http.py", line 638, in request
    async with self.__session.request(method, url, **kwargs) as response:

  File "/home/user/.local/lib/python3.10/site-packages/aiohttp/client.py", line 1197, in __aenter__
    self._resp = await self._coro

  File "/home/user/.local/lib/python3.10/site-packages/aiohttp/client.py", line 507, in _request
    with timer:

  File "/home/user/.local/lib/python3.10/site-packages/aiohttp/helpers.py", line 715, in __enter__
    raise RuntimeError(

RuntimeError: Timeout context manager should be used inside a task
python-3.x discord.py python-asyncio py-langchain
1个回答
0
投票

如果您需要从尚未直接或间接从异步函数调用的函数或方法中调用异步函数或方法(即调用

asyncio.get_running_loop()
会引发异常),那么您只需使用
asyncio.run 
方法。以下是当您不知道如何调用非异步函数时的更一般情况。

在此演示中,我有一个函数

bar
想要调用异步函数
foo
。有几种情况:

  1. bar
    在模块级别调用(非异步调用)。
  2. bar
    由非异步函数
    foobar
    调用,该函数在模块级别调用。
  3. bar
    foobar
    都是从异步函数调用的。当
    bar
    async
    函数直接或间接调用时,它会创建一个由其异步调用者返回并等待的任务。
import asyncio
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

def run_async_from_non_async(async_fn, *args, **kwargs):
    try:
        # This can cause a deprecated warning and in the future
        # will raise an exception:
        loop = asyncio.get_running_loop()
        print("asyncio.get_running_loop() succeeded")
        return asyncio.create_task(async_fn(*args, **kwargs))
    except RuntimeError:
        print('asyncio.get_running_loop() failed')
        try:
            # Depending on what version of Python you are running
            # this can result in a Deprecated Warning or raise
            # an exception (in a future release)
            loop = asyncio.get_event_loop()
        except RuntimeError:
            print('No event loop')
            return asyncio.run(async_fn(*args, **kwargs))
        else:
            if loop.is_running():
                # An event loop is running.
                # We must have been called directly or indirectly
                # from an async function. We just return a task that can
                # be awaited:
                print('asyncio.get_event_loop() returned a running loop')
                return asyncio.create_task(async_fn(*args, **kwargs))
            else:
                # An event loop was created but is no longer running.
                # Use it to run the async function:
                print('asyncio.get_event_loop() returned a non-running loop')
                return loop.run_until_complete(async_fn(*args, **kwargs))


async def foo(x):
    await asyncio.sleep(1)
    return x ** 2

def bar(x):
    result = run_async_from_non_async(foo, x)
    if asyncio.isfuture(result):
        return result;  # return back to caller
    print('result =', result)

# Call the non-async function from a non-async function, which
# may or may not have been called directly or indirectly from an async function:
def foobar(x):
    result = bar(x)
    if asyncio.isfuture(result):
        return result;  # return back to caller

# Example usage from an async function
async def main():
    print('result =', await bar(10))  # Call bar from async function
    print('result =', await foobar(11))  # Call foobar from non-async function

# Running the example usage
print('result =', bar(7))  # Call bar from non-async function
print('result =', bar(8))  # Call bar from non-async function
foobar(9)  # Call foobar from non-async function
asyncio.run(main())

打印:

asyncio.get_running_loop() failed
asyncio.get_event_loop() returned a non-running loop
result = 49
result = None
asyncio.get_running_loop() failed
asyncio.get_event_loop() returned a non-running loop
result = 64
result = None
asyncio.get_running_loop() failed
asyncio.get_event_loop() returned a non-running loop
result = 81
asyncio.get_running_loop() succeeded
result = 100
asyncio.get_running_loop() succeeded
result = 121
© www.soinside.com 2019 - 2024. All rights reserved.