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

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

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

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

929閲覧

Python 非同期通信でタスクオブジェクトが返ってくる際の対処法

paopao7

総合スコア29

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2022/01/31 15:59

編集2022/02/03 15:41

pythonの非同期通信に関して勉強し始めました。試しに色々書いてみているのですが、つまづいてしまったので質問させて頂きます。

Python

1async def main(): 2 var = 'おはよう' 3 4 while True: 5 6 task1 = asyncio.create_task(coro_1()) 7 var = task1 8 9 task2 = asyncio.create_task(coro_2(var)) 10 await task2 11 12 task3 = asyncio.create_task(coro_3()) 13 await task3 14 15 16async def coro_1(): 17 await asyncio.sleep(3) 18 var = 'こんばんは' 19 return var 20 21 22async def coro_2(var): 23 await asyncio.sleep(1) 24 pybotters.print(var) 25 26 27async def coro_3(): 28 await asyncio.sleep(1) 29 pybotters.print('こんにちは') 30 31 32 33try: 34 asyncio.run(main()) 35except KeyboardInterrupt: 36 pass

上記のコードで、coro_1関数でプログラム開始から3秒後にvarに'こんばんは'を代入し、printすること想定して書いてみました。(おはよう→こんにちは→こんにちは→こんばんは)のようにprintされると考えていました。

ですが、task1ではawaitをしていないためか、varにはタスクオブジェクトが代入されてしまいます。

awaitをするとプログラム開始から3秒後に'こんばんは'がvarに代入され、'おはよう'がprintされません。(こんばんは→こんにちは→こんばんは→....)となってしまいます。

私の想定通りにprintするにはどうすれば良いのでしょうか。

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

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

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

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

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

guest

回答2

0

Python 非同期通信でタスクオブジェクトが返ってくる際の対処法

タスク内で return される値を扱うには、
task.add_done_callback メソッドで、タスク完了時に呼び出す関数を登録します。

python

1import asyncio 2from types import SimpleNamespace 3 4 5async def main(): 6 shared = SimpleNamespace( 7 var="おはよう", 8 ) 9 10 task1 = asyncio.create_task(coro_1()) 11 12 def done(task): 13 shared.var = task.result() 14 15 task1.add_done_callback(done) 16 17 while True: 18 task2 = asyncio.create_task(coro_2(shared)) 19 20 await task2 21 22 task3 = asyncio.create_task(coro_3()) 23 24 await task3 25 26 27async def coro_1(): 28 await asyncio.sleep(3) 29 return 'こんばんは' 30 31 32async def coro_2(shared): 33 await asyncio.sleep(1) 34 print(shared.var) 35 36 37async def coro_3(): 38 await asyncio.sleep(1) 39 print('こんにちは') 40 41 42try: 43 asyncio.run(main()) 44except KeyboardInterrupt: 45 pass 46

質問文: (おはよう→こんにちは→おはよう→こんにちは→こんばんは)のようにprintされると考えていました。
コメント: 私が想定していたのはおはよう→こんにちは→こんばんは→こんにちは→こんばんは→...というようにvarにはawaitの処理が終わるまでは「おはよう」、終わってからは「こんばんは」が代入されるというものでした。

想定する挙動がどちらか解りませんが、共通する問題点として

  • coro_2 に渡す引数、コルーチン生成の時点でのvarの値が渡されます。

 coro_1 の結果を反映することができません。→ 対策: 辞書に入れて、引数には辞書を渡す等

  • await task では、処理を中断し、右辺に指定された コルーチンの完了を待ちます

 質問に掲載のコードでは task2 と task3が完了する迄、次のループに移りません。

main側のループで実行したい場合は、メインのループの末尾で
await asyncio.sleep(適当な数値) としておけば、タスクの完了は待たずに次のループを実行できます。
(await task ではタスク完了を待ってしまう。await なしではコルーチンが実行されない。)
但し、ループ内で使う変数の上書き、デストラクタ等の注意が必要になったりする為、幾つか注意が必要。

main関数側のループで扱うのではなく、各コルーチン内で独立したループを持つ方法をお勧めします。
「〇〇秒後に実行」であれば、簡易的にはloop.call_later を使う方法もあります。

投稿2022/02/01 07:00

編集2022/02/01 08:27
teamikl

総合スコア8664

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

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

paopao7

2022/02/03 13:38

ご回答ありがとうございます!なるほど、非同期にも様々なメソッドがあるんですね。 やっぱりコルーチン側でループさせるべきなんですね。色んなサイトを見てもmain側でループさせているのは少なかったです...笑 初歩的な質問で申し訳ないんですが、コルーチン側でループさせるとすると、処理の順番が自分の想定とずれてしまうんじゃないかと思ってしまうんですが、どうなんでしょうか?
paopao7

2022/02/03 13:59

それと、main側でループさせるとするなら「await asyncio.sleep(適当な数値) としておけば、タスクの完了は待たずに次のループを実行できます。」とのことですが、例えばawait task2(何かしら時間のかかる処理だとして)がmainループにあるとすると、次のループに移ってから、task2の処理はまた初めから為されるということでしょうか?
teamikl

2022/02/04 12:20

処理の順番は、想定されている順序がどちらか解らないので正解が解りませんが、 実行順序が重要になる場合は「非同期」ではなく「同期」コードとします。 現状のコードでは・・・ await task2 であ task2の完了を待ってから task3 を作る「同期」コードとなっているので、 分離する必要が無ければ一つのループに統合できます。 2つのコルーチンで実装する場合の例 - 処理が終わった後(3秒後) に変数を「こんばんは」に変更  ※ 3秒後というのが、何らかの時間の掛かる処理の場合は、別スレッドを使ったりもう少し複雑になります。 -「こんにちは」と変数を1秒毎に交互に表示するループ >例えばawait task2(何かしら時間のかかる処理だとして)がmainループにあるとすると、 >次のループに移ってから、task2の処理はまた初めから為されるということでしょうか? いいえ、この場合想定する前提が違い、 await task2 とはせずに、代わりに毎ループ await asyncio.sleep(n) とします。 task2を新たにループ毎に作るかどうかは、想定する挙動次第ですが、 task2の処理が勝手に巻き戻り最初から実行されることは、ありません。
guest

0

ベストアンサー

var = task1おはよう が入っている変数が上書きされてしまうので、おはよう がprintされることはありませんね。

(おはよう→こんにちは→おはよう→こんにちは→こんばんは)のようにprintされると考えていました。

おはよう→こんにちは→こんばんは→おはよう→こんにちは→こんばんは の順ではないですか?
それならこれでどうでしょうか?

py

1import asyncio 2 3 4async def main(): 5 var = 'おはよう' 6 7 while True: 8 task1 = asyncio.create_task(coro_1()) 9 task2 = asyncio.create_task(coro_2(var)) 10 task3 = asyncio.create_task(coro_3()) 11 print(await task1) 12 await task2 13 await task3 14 15 16async def coro_1(): 17 await asyncio.sleep(3) 18 return 'こんばんは' 19 20 21async def coro_2(var): 22 await asyncio.sleep(1) 23 print(var) 24 25 26async def coro_3(): 27 await asyncio.sleep(1) 28 print('こんにちは') 29 30 31try: 32 asyncio.run(main()) 33except KeyboardInterrupt: 34 pass

こうすれば最初の1回だけ 'おはよう' が表示されます。

py

1import asyncio 2 3 4async def main(): 5 var = 'おはよう' 6 7 while True: 8 task1 = asyncio.create_task(coro_1()) 9 task2 = asyncio.create_task(coro_2(var)) 10 task3 = asyncio.create_task(coro_3()) 11 var = await task1 12 await task2 13 await task3 14 15 16async def coro_1(): 17 await asyncio.sleep(3) 18 return 'こんばんは' 19 20 21async def coro_2(var): 22 await asyncio.sleep(1) 23 print(var) 24 25 26async def coro_3(): 27 await asyncio.sleep(1) 28 print('こんにちは') 29 30 31try: 32 asyncio.run(main()) 33except KeyboardInterrupt: 34 pass

Taskが終わっていたらawaitするようにしたら望みの動作になりますか?

py

1import asyncio 2 3 4async def main(): 5 var = 'おはよう' 6 task1 = task2 = task3 = None 7 8 while True: 9 if task1 is None: 10 task1 = asyncio.create_task(coro_1()) 11 if task2 is None: 12 task2 = asyncio.create_task(coro_2(var)) 13 if task3 is None: 14 task3 = asyncio.create_task(coro_3()) 15 if task1.done(): 16 var = await task1 17 task1 = None 18 if task2.done(): 19 await task2 20 task2 = None 21 if task3.done(): 22 await task3 23 task3 = None 24 await asyncio.sleep(0.1) 25 26 27async def coro_1(): 28 await asyncio.sleep(3) 29 return 'こんばんは' 30 31 32async def coro_2(var): 33 await asyncio.sleep(1) 34 print(var) 35 36 37async def coro_3(): 38 await asyncio.sleep(1) 39 print('こんにちは') 40 41 42try: 43 asyncio.run(main()) 44except KeyboardInterrupt: 45 pass

少し簡略化

py

1import asyncio 2 3 4async def main(): 5 var = 'おはよう' 6 task1 = None 7 8 while True: 9 if task1 is None: 10 task1 = asyncio.create_task(coro_1()) 11 task2 = asyncio.create_task(coro_2(var)) 12 task3 = asyncio.create_task(coro_3()) 13 if task1.done(): 14 var = await task1 15 task1 = None 16 await task2 17 await task3 18 19 20async def coro_1(): 21 await asyncio.sleep(3) 22 return 'こんばんは' 23 24 25async def coro_2(var): 26 await asyncio.sleep(1) 27 print(var) 28 29 30async def coro_3(): 31 await asyncio.sleep(1) 32 print('こんにちは') 33 34 35try: 36 asyncio.run(main()) 37except KeyboardInterrupt: 38 pass

投稿2022/01/31 17:39

編集2022/02/01 03:21
shiracamus

総合スコア5406

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

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

paopao7

2022/01/31 18:38

私が想定していたのはおはよう→こんにちは→こんばんは→こんにちは→こんばんは→...というようにvarにはawaitの処理が終わるまでは「おはよう」、終わってからは「こんばんは」が代入されるというものでした。 coro_1関数内で、await asyncio.sleep(3)としているため、varにはその時点では'こんばんは'は代入されず、awaitの処理が終わったら代入されると考えていたのですが、代入されてしまうんですね。 最終的にやりたいことは、awaitの処理が含まれるコルーチン関数は、awaitの処理が終わるまでは実行されず、他のコルーチン関数をmainのループで実行し続ける、というようなことなんですが、その場合はどうすればいいんでしょうか...?
shiracamus

2022/02/01 02:41 編集

await することでひとつの Task が動きます。 そのTaskの中で await すると他の Task が動きます。 await しない限り、他の Task は動けないです。 mainのループを実行し続けるには、作成した Task を await しつつ、ループ1回ごとに await して Task に実行権を譲渡しないといけません。 Task が終わっていたら await するようにすることで望みの動作になりますでしょうか? 回答にコードを追記しておきました。
paopao7

2022/02/01 06:08

なるほど、工夫が必要なんですね。ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問