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

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

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

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

3回答

2797閲覧

Unityのhttp通信の完了を待って次の処理ができない

kz23szk

総合スコア85

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2017/08/07 01:24

編集2017/08/07 02:11

Unityにてhttp通信でテキストファイルを取得して変数に書き込みたいのですが、UnityWebRequestを使ってGETしてみると非同期処理になってしまうようで,ソースの一番下のDebug.Logで表示すると空のテキストが表示されてしまいます。

Sendの処理の完了を待つようにできないでしょうか?
ご教授よろしくお願いいたします。

c#

1using UnityEngine.Networking; 2 3void Start () { 4 ReadData(); 5} 6 7public string textdata = ""; 8 9public void ReadData(){ 10 StartCoroutine (GetProblem ()); 11 // ↓このdebug.logでtextdataが空になってしまう 12 Debug.Log ("finished: " + textdata); 13} 14 15public IEnumerator GetProblem(){ 16 UnityWebRequest www = UnityWebRequest.Get ("http://xxx.xxx.xxx.xxx/xxx.txt"); 17 yield return www.Send (); 18 19 if (www.isError) { 20 Debug.Log (www.error); 21 } else { 22 textdata = www.downloadHandler.text; 23 // ここでは表示される Debug.Log (textdata); 24 } 25} 26

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

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

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

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

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

guest

回答3

0

全体の処理をコルーチンで記述するか、コールバックを設けるのが一般的でしょうか

・全体をコルーチンで

C#

1public string textdata = ""; 2 3IEnumerator Start () { 4 yield return GetProbrem(); 5 //通信後の処理をそのまま流れで書ける 6 Debug.Log ("finished: " + textdata); 7} 8 9public IEnumerator GetProblem(){ 10 UnityWebRequest www = UnityWebRequest.Get ("http://xxx.xxx.xxx.xxx/xxx.txt"); 11 yield return www.Send (); 12 13 if (www.isError) { 14 Debug.Log (www.error); 15 } else { 16 textdata = www.downloadHandler.text; 17 // ここでは表示される Debug.Log (textdata); 18 } 19}

・コールバックで書く

C#

1 2void Start () { 3 ReadData(); 4} 5 6public void ReadData(){ 7 StartCoroutine (GetProblem (txt => { 8 //通信結果をコールバックとして受け取る 9 Debug.Log ("finished: " + txt); 10 })); 11} 12 13public IEnumerator GetProblem(System.Action<string> callback){ 14 UnityWebRequest www = UnityWebRequest.Get ("http://xxx.xxx.xxx.xxx/xxx.txt"); 15 yield return www.Send (); 16 17 if (www.isError) { 18 Debug.Log (www.error); 19 } else { 20 callback.Invoke(www.downloadHandler.text); 21 } 22} 23 24

個人的にはコールバックを多用するとネストが深くなって読みにくくなるので、コルーチンでやるのが好きですね

投稿2017/08/07 03:30

編集2017/08/07 03:31
ShiroKuroShiro

総合スコア134

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

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

HiroshiWatanabe

2017/08/09 06:15

質問内容と回答が合致していないようです
fiveHundred

2017/08/10 00:08

私は合致していると思いますよ。 Debug.Logの位置は変わっていますが、どちらも「Sendの処理の完了を待ってから、Debug.Logする」という感じになっております。
HiroshiWatanabe

2017/08/10 01:50

sendの処理を待つのと結果の受信完了を待つのは同じでは無いと思ってたんですがこれで(isDoneも見ずに)正しく内容受信完了まで待てているんでしょうか…?
ShiroKuroShiro

2017/08/15 03:37

yield return www.Send();がそもそもSend処理終了まで(isDoneがTrueになるまで)待つ処理ですので問題ないはずです。 isDoneは、Send処理中に何かしたいときに使うイメージですね。
guest

0

提示されたソースだと実行できません。
yield return www.Send ();IEnumerator内でしか動作しません。
}が1つ多いです。

なので、推測ですが、

C#

1 string textdata = ""; 2 3 UnityWebRequest www = UnityWebRequest.Get ("http://xxx.xxx.xxx.xxx/xxx.txt"); 4 yield return www.Send (); 5 6 if (www.isError) { 7 Debug.Log (www.error); 8 } else { 9 textdata = www.downloadHandler.text; 10 }

以上の処理は、IEnumerator内に書かれていると思います。
これは、コルーチンというもので、通常の関数とは異なります。
この場合、yield return www.Send ();では、送信処理を行うだけではなく、受信するまで待機する、ということも行われます。
そのため、受信し終わってから、if (www.isError) {以下の処理が行われます。
受信するまでは、Unity上ではUpdate()などの他の処理が行われます。

なので、textdataに何か文字列が入ってからDebug.Log(textdata);を実行するというような処理にする必要があります。

投稿2017/08/07 01:56

fiveHundred

総合スコア9797

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

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

kz23szk

2017/08/07 02:16

申し訳ありません、ソースが間違っていました。質問内のソースを修正したので見ていただきたいのですが、ご指摘の通りhttpでの取得はIEnumerator内で行なっていましてその中では逐次実行しています。 やりたいこととしてはReadData()のStartCorutine()以下に取得したデータを使って処理を進めたいのですが、コルーチン処理なので非同期で切り離され、Debug.Log ("finished: " + textdata);のところのtextdataは空のままになってしまいます。コルーチンの処理を待って次の処理を実行することはできないでしょうか?
fiveHundred

2017/08/07 02:53

思いついた限りでは、以下の方法が考えられます。 ・Update()内に「もし、textdataが変更されていれば、Debug.Log()する」という処理を書く ・Debug.Log()をGetProblem()の中に書いてしまう ・「GetProblem()の実行が完了してから、Debug.Log()する」という内容の、新しいコルーチンを作る 質問者さんの意図を私なりにくみ取ってみた感じだと、やりたいことに近いのはおそらく3番目だと思います。 ちなみに、GetProblem()の実行が完了するまで待つ、ということはHiroshiWatanabeさんの「isDone()」を使う方法の他にも、「yield return GetProblem();」と書く方法もあります。
guest

0

ベストアンサー

通信が終了したかを isDone で判断して(isDoneがtrueになるまでは yield return で待つようなループにして)から終了後のデータ参照(やエラー判定)をしてはどうでしょうか?
UnityWebRequest.isDone

(具体的事例を追記)

c#

1yield return www.Send(); 2 3```と 4```c# 5if (www.isError) { 6 7```の間に 8```c# 9while (!www.isDone) { 10 yield return null; 11} 12 13```を入れる

投稿2017/08/07 02:41

編集2017/08/07 04:39
HiroshiWatanabe

総合スコア2160

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

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

kz23szk

2017/08/07 09:08

GetProblem内で while (!www.isDone) { //yield return null; } として 最後に yield return null;を入れると取得するまでは待って、 ReadData()内のDebug.Logで表示されるようになりました。 ありがとうございます。
fiveHundred

2017/08/07 11:25

本当に正しい判断ですか? ループ内の「yield return null;」を消すとUnity全体が固まりますよ
HiroshiWatanabe

2017/08/09 00:55

ループ内のyield return null; が無いとiOS等環境によってはそこでフリーズして進行不能になったりするリスクがありますので外さない方がいいと思いますよ
fiveHundred

2017/08/10 00:14

追記すると、 「yield ~ ;」は「他の処理をしながら、待機する」です。 「yield ~ ;」をせずに何秒もかかる処理を行ってしますと、出力したいDebug.Logだけでなく、他のオブジェクトのUpdate()処理なども処理されなくなってしまいます。 もしかすると、その中にUnityの重要な処理が含まれているかもしれません。 そのため、Unityでは「~している間待機する」といった処理はコルーチン内で「yield ~ ;」を使うことになっております。 使い方は、ShiroKuroShiroさんの回答の1番目のソースを確認してください。 ちなみに、結果的にHiroshiWatanabeさんの回答が間違えということになりますが、「yield return www.Send();」の時点で「受信まで待機する」という処理が行われていますので、「while (!www.isDone) { yield return null; }」は使う必要はないです。
HiroshiWatanabe

2017/08/10 01:56 編集

上にも記載しましたがsendの終了をまつ事と結果のテキスト受信完了を待つ事は同じなのでしょうか?元々の質問者さんはyield return www.Send ();しているのに結果の受取完了していない状態でその後のログ出力で参照されているという状況で質問に至ったようですしyield return で指定するのがwww自体ならわかるのですがwww.Sendを待つだけではisDoneでの判定が必要なのだろうと解釈したんですが…しかし実際の所は私も普段はWWWクラスしか利用しておらずUnityWebRequest は使った事がないため未検証な想像でした。間違った回答になっていたのなら申し訳ありませんでした。
fiveHundred

2017/08/10 06:56

私が試してみた限りでは、Send()だけでも文字列の受け取りは問題なくできました。 また、質問者さんのソースでも、「yield return www.Send ();」の直後に「// ここでは表示される Debug.Log (textdata);」と書いているので、文字列自体は受け取れてはいるのではないかと思います。 なので、私はisDoneの判定は必要ないのではないか、と判断しました。 私の解釈では、質問者さんの意図は「StartCoroutine()の直後にDebug.Log()すると何も出力されない」だと思っております。 そうすれば、「yield return null;」を消した理由も以下のように推測できますので、妥当ではないかと。 「yield return null;」のタイミングで「Debug.Log」が実行されてしまうため、何も表示されない →「yield return null;」を消したらwhileループの間は、「Debug.Log」が実行されない(Unity全体が止まっているから)ため、うまくいった(ように見える)
HiroshiWatanabe

2017/08/10 09:34

なるほど理解できました。お手数お掛けしました。
kz23szk

2017/08/11 00:49

すみません。解決できたと思っていたのでチェックしておりませんでした。 たしかにyield return null;を外すともしDoneにならないときに固まってしまうため時間計測して一定時間がたった場合は抜けるように処理を追加しました。 System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch (); sw.Start(); while (!www.isDone && sw.Elapsed.TotalSeconds < 20.0) { } としています。 Debug.Log ("finished: " + textdata);のところにtextdataを元にした処理を入れたいのでyield return nullで抜けてしまうとtextdataが空の状態で処理が進んでしまい困っていました。 本当はネットワーク処理の部分をコルーチンでなくかければよいのですが。
fiveHundred

2017/08/11 01:17

その方法でも最大20秒間、Unity『全体』が固まってしまいます。 ネットワーク処理の部分がコルーチンになっている理由は、受信中に他の処理を行うことが出来なくなるからです。 Unityの基本的な処理はシングルスレッドなので、「yield ~ ;」としないと、他の処理に移ることが出来ません。 「~まで待機する」という処理は正しくコルーチンが得意とする点なので、コルーチンに書き換えてしまうのが一番だと思います。
kz23szk

2017/08/11 01:58

サーバーから取得したtextdataにクイズの問題が入っており、そのtextdataがないとゲーム画面のクイズ出題部分が空白になってしまいます。なのでunity全体が固まっても仕方がないかなと考えていました。 ロード画面を表示しておいて、コルーチンで裏で取ってきて、取得でき次第表示の方が健全(?)でしょうか?
fiveHundred

2017/08/11 06:56

Unity全体が固まってしまうと、システムの不具合が起きるかもしれませんし、そうでなくともユーザーが不具合だと思われる可能性もあります。 私としては、前の問題の最中にコルーチンで問題を取ってきてしまうのがいいと思います。
kz23szk

2017/08/14 01:15

なるほど。コルーチンで取れるように処理を修正してみようと思います。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問