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

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

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

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

Python 3.x

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

解決済

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

ngmg
ngmg

総合スコア67

Discord

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

Python 3.x

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

2回答

0グッド

0クリップ

262閲覧

投稿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("こんにちは")

以下のような質問にはグッドを送りましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

グッドが多くついた質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

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

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

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

下記のような質問は推奨されていません。

  • 間違っている
  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

回答2

1

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

総合スコア8534

qnoir👍を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

ngmg

2022/12/08 15:18

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

1

ベストアンサー

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
qnoir

総合スコア7759

teamikl👍を押しています

良いと思った回答にはグッドを送りましょう。
グッドが多くついた回答ほどページの上位に表示されるので、他の人が素晴らしい回答を見つけやすくなります。

下記のような回答は推奨されていません。

  • 間違っている回答
  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

teamikl

2022/12/08 14:35

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

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

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

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

ただいまの回答率
86.12%

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

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

質問する

関連した質問

同じタグがついた質問を見る

Discord

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

Python 3.x

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