質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Discord

Discordは、ゲーマー向けのボイスチャットアプリです。チャット・通話がブラウザ上で利用可能で、個人専用サーバーも開設できます。通話中でも音楽を流したり、PC画面を共有できるなど多機能な点が特徴です。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

2回答

788閲覧

python sleepしているプログラムをやめさせたい

ngmg

総合スコア72

Discord

Discordは、ゲーマー向けのボイスチャットアプリです。チャット・通話がブラウザ上で利用可能で、個人専用サーバーも開設できます。通話中でも音楽を流したり、PC画面を共有できるなど多機能な点が特徴です。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2022/12/08 12:06

編集2022/12/08 12:19

実現したいこと

sleepで待機しているプログラムを途中でやめさせることはできるのでしょうか?
ディスコードで「こんにちは」と送信されたら20秒後に「こんにちは」と返すようにしているのですが
「こんにちは」と送信した人がそのメッセージを消すと「こんにちは」と返さないようにしたいです?

該当のソースコード

#メッセージが送信されたら async def on_message(message: discord.Message): # botを対象からはずす if message.author.bot: return  #「こんにちは」と送信されたら if message.content == "こんにちは": await asyncio.sleep(20)    #「こんにちは」を投稿する await message.channel.send("こんにちは")

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

asyncio.sleep の代わりに asyncio.Event.wait / asyncio.wait_for を使いましょう

asyncio.Event

  • wait()

内部フラグが真になるまでブロックします。

  • set()

内部フラグを真に設定します。それを待っていたすべてのコルーチンが再開します。wait() を呼び出していたコルーチンへのブロックが解除されます。

asyncioのEvent.waitは timeout 引数を取らないので、
wait_for により timeout することで、sleep と同等の待機をします。
タイムアウト時は asyncio.TimeoutError 例外が投げられる点に注意。

python

1 2import asyncio 3 4 5# threading.Event と同じような wait に timeout を設定できるようにする場合 6class Event(asyncio.Event): 7 async def wait(self, timeout=None): 8 try: 9 await asyncio.wait_for(super().wait(), timeout) 10 except asyncio.TimeoutError: 11 pass 12 13 14 15async def main(): 16 from time import time 17 18 start_time = time() 19 20 event = asyncio.Event() 21 loop = asyncio.get_running_loop() 22 23 # 3秒後に解除 24 loop.call_later(3, event.set) 25 26 # 5秒間スリープ 27 try: 28 event.clear() 29 await asyncio.wait_for(event.wait(), 5) 30 except asyncio.TimeoutError: 31 pass 32 print(time() - start_time) 33 34 ev = Event() 35 loop.call_later(3, ev.set) 36 await ev.wait(timeout=5) 37 38 39if __name__ == '__main__': 40 asyncio.run(main()) 41 42``' 43 44sleepしたい場所で wait_for/event.wait 45キャンセルしたい場合は、event.set を呼び出し、 46event.is_set により、キャンセルされたかどうかを判別できます。 47 48質問のコードに適応させるには、メッセージのキャンセルを受ける側の関数への 49event オブジェクトの受け渡しに工夫が必要になると思います。 50関数外にuser毎に辞書に格納する、等。

投稿2022/12/08 14:25

teamikl

総合スコア8664

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ngmg

2022/12/08 15:18

sleepではないやり方は全く考えつかなかったです。参考にして勉強したいと思います。 ありがとうございました!
guest

0

ベストアンサー

asyncio.sleepを直接キャンセルするような処理は、sleepを使用している全ての処理に影響を与えるため、あまり推奨されません。
識別子を入れる等して回避する方法はありますが複雑になります。
https://stackoverflow.com/questions/37209864/interrupt-all-asyncio-sleep-currently-executing

よりシンプルには、下記のようにasyncio の wait_for と Event を使う方法があります。

  • メッセージID に対応したイベントごとに20秒間待ち、その間に削除された場合は、そのメッセージに対する返答をキャンセルします。

  • 20秒の間に削除されなかった場合はタイムアウトとなり、返答されることになります。

python

1import discord 2import asyncio 3from asyncio import wait_for, Event 4 5TOKEN = 'discord botのトークン' 6intents = discord.Intents.default() 7intents.message_content = True 8client = discord.Client(intents=intents) 9events = {} # メッセージIDに対応するイベントを格納する辞書 10 11@client.event 12async def on_message_delete(message): 13 if message.id in events: 14 # 削除されたメッセージに対応する event を set。 15 events[message.id].set() 16 print("メッセージが削除されました") 17 18 19@client.event 20async def on_message(message): 21 # botを対象からはずす 22 if message.author.bot: 23 return 24 #「こんにちは」と送信されたら 25 if message.content == "こんにちは": 26 try: 27 # 20秒の間、削除を待つ。 28 events[message.id] = Event() 29 await wait_for(events[message.id].wait(), timeout=20) 30 except asyncio.exceptions.TimeoutError: 31 # 20秒の間にメッセージが削除されなかった場合、botに返事させる。 32 await message.channel.send('こんにちは') 33 finally: 34 events.pop(message.id) 35 36 37client.run(TOKEN)

投稿2022/12/08 14:23

編集2022/12/08 14:46
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teamikl

2022/12/08 14:35

on_message_delete へ受け渡す方法までは、思い浮かばなかったけど、 メッセージID の辞書の案よいですね。
退会済みユーザー

退会済みユーザー

2022/12/08 14:53 編集

コメントありがとうございます! teamikl さんの、クラスを継承して汎用化する形も勉強になりました。 今気づいたんですがこの場合 .clear() は必要ないですよね・・・(events.pop(message.id)だけでよい)
teamikl

2022/12/08 15:11

再利用しないなら clear は不要ですね。popの戻り値も不要なので Event() もいらないかも。 若干オフトピで 辞書は、weakref.WeakValueDictionary を使うと 辞書からの削除もGC任せにできて pop も省けます。 wait() 前に、フラグ状態を確定させるために clear() を入れることはあるようです。 (不具合があった場合の安全策として) 今回の場合、直近でインスタンスを作っているので、なくても問題ありません。
ngmg

2022/12/08 15:17

こちらの方法で理想の動作ができました。 理解できるように頑張りたいと思います。 ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問