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

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

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

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

C#

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

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Unity

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

Q&A

解決済

1回答

2467閲覧

voidメソッドの中で非同期で取得する値を待ちたい

_Beginner

総合スコア103

Firebase

Firebaseは、Googleが提供するBasSサービスの一つ。リアルタイム通知可能、並びにアクセス制御ができるオブジェクトデータベース機能を備えます。さらに認証機能、アプリケーションのログ解析機能などの利用も可能です。

C#

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

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Unity

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

0グッド

1クリップ

投稿2020/03/16 13:29

編集2020/03/22 14:17

現在Unityでゲームを作っており、Firebaseを使ってデータの管理をしようと考えています。
実際にデータの登録はできる状態で、取得するところで詰まっています。

登録したデータを呼び出すのに、以下のコードを使用しています。

あるタイミングでGetValue()を実行し、非同期でGetIntOfInformationを呼び出し、代入しています。

C#

1 int i = 0; 2 void GetValue(string path) 3 { 4 i = GetIntOfInformation(path); 5 Debug.Log(i); 6 } 7 8 public int GetIntOfInformation(string path) 9 { 10 int num = 0; 11 FirebaseDatabase.DefaultInstance 12 .Child("Information") 13 .Child(path) 14 .GetValueAsync().ContinueWith(task => 15 { 16 if (task.IsFaulted) 17 { 18 Debug.Log("Faulted"); 19 } 20 else if (task.IsCompleted) 21 { 22 snapshot = task.Result; 23 num = int.Parse(snapshot.ToString()); 24 } 25 }); 26 27 return num; 28 }

しかしこのやり方だと、非同期で値をサーバーから取得している間にGetValueの流れは終わってしまい、結果0のままです。

実際にコード中の**num = int.Perse(snapshot.ToString())**の下にデバッグを入れるとサーバーに保存されている数値が表示されるので間違ってはいないと思います。

**GetValue()**の流れを、numに値が入って戻り値となるまで止めることはできるのでしょうか。

##追記

**num = int.Perse(snapshot.ToString())の下とかにGotValue(num)**なんて付けて、

C#

1 void GotValue(int i) 2 { 3 Debug.Log(i); 4 }

こんな風にしたら楽なんですけど、取得したい値のキーはこれだけじゃなくて、汎用性がなくなっちゃうので、非同期処理を待つようなことはできないか知りたいです。

##追記2

####例外 Input string was not in a correct format. が発生する件について

C#

1 void Start() 2 { 3 Invoke("GetVal", 5); 4 } 5 6 private async void GetVal() 7 { 8 int getScore = await FirebaseController.GetIntOfInformation("Score"); 9 Debug.Log("Score"); //ここでスコアを取得したい 10 } 11 12 public async Task<int> GetIntOfInformation(string name) 13 { 14 try 15 { 16 var snapshot = await FirebaseDatabase.DefaultInstance 17 .GetReference("USERS") 18 .Child(id) //ユーザー識別(FirebaseAuthment)で取得したユーザーid 19 .Child("Information") 20 .Child(name) 21 .GetValueAsync().ConfigureAwait(false); 22 23 return int.Parse(snapshot.ToString()); 24 } 25 catch (Exception exception) 26 { 27 Debug.LogError(exception); 28 return 0; 29 } 30 31 }

このコードで**Debug.LogError(exception);**が常に呼び出されてしまいます。
Firebaseコンソール上では __USERS/"ユーザーid"/Information/Score__に値が入っています。

ちなみに、例外処理が起きるということは戻る数値も正しくないのでエラーにしちゃったほうがいいですよね?
(適当な数字を戻して上書きしてしまったりするのを防ぐにはエラーにする以外あるのでしょうか。)

######エラー(例外)内容

System.FormatException: Input string was not in a correct format.

at System.Number.StringToNumber (System.String str, System.Globalization.NumberStyles options, System.Number+NumberBuffer& number, System.Globalization.NumberFormatInfo info, System.Boolean parseDecimal) [0x00057] in <567df3e0919241ba98db88bec4c6696f>:0
at System.Number.ParseInt32 (System.String s, System.Globalization.NumberStyles style, System.Globalization.NumberFormatInfo info) [0x00013] in <567df3e0919241ba98db88bec4c6696f>:0
at System.Int32.Parse (System.String s) [0x00007] in <567df3e0919241ba98db88bec4c6696f>:0
at FirebaseController+<GetIntOfInformation>d__10.MoveNext () [0x000cd] in C:\Users\users\UnityProjects\project\Assets\FirebaseController.cs:133
UnityEngine.Debug:LogError(Object)
<GetIntOfInformation>d__10:MoveNext() (at C:/Users/user/UnityProjects/TestProject/Assets/FirebaseController.cs:137)
System.Threading.Tasks.TaskCompletionSource1:SetResult(DataSnapshot) Firebase.Database.<WrapInternalDataSnapshotTask>c__AnonStorey0:<>m__0(Task1) (at Z:/tmp/tmp.hapPVC8cw6/firebase/database/client/unity/proxy/Query.cs:139)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback()

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

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

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

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

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

guest

回答1

0

ベストアンサー

こんにちは。

提示のコードには一部不備があるため、推測を含みますが回答してみます。

呼び出し元から直接呼び出す対象が GetValue メソッドであることと、GetValue メソッドの処理が非同期に投げっぱなしになっていれば良いのであれば、両メソッドを非同期メソッドに直し、await を使えば良いです。
具体的には、メソッドシグネチャに async を付け、戻り値を Task にし、該当の非同期処理を await で待機するだけです。
ただ直しただけなら以下のようになります。

csharp

1 int i = 0; 2 async void GetValue() 3 { 4 i = await GetIntOfInformation(/* 何を渡す? */); 5 Debug.Log(i); 6 } 7 8 public async Task<int> GetIntOfInformation(string path/* 何にも使われていない */) 9 { 10 int num = 0; 11 await FirebaseDatabase.DefaultInstance 12 .GetReference(id) // id や name がフィールドなのか? 13 .Child("Information") 14 .Child(name) 15 .GetValueAsync().ContinueWith(task => 16 { 17 if (task.IsFaulted) 18 { 19 Debug.Log("Faulted"); 20 } 21 else if (task.IsCompleted) 22 { 23 snapshot = task.Result; 24 num = int.Parse(snapshot.ToString()); 25 } 26 }); 27 28 return num; 29 }

現状のコードだと、int.Parse が失敗したときに観測されない非同期例外が発生するなどの問題があるため、読みやすさ等も考慮して真っ当な形に書き直すとしたら、こんな感じにしますね。

csharp

1public async Task<int> GetIntOfInformation(string path, string name) 2{ 3 try 4 { 5 var snapshot = await FirebaseDatabase.DefaultInstance 6 .GetReference(path) 7 .Child("Information") 8 .Child(name) 9 .GetValueAsync().ConfigureAwait(false); 10 11 return int.Parse(snapshot.ToString()); 12 } 13 catch (Exception exception) 14 { 15 Debug.Log("Faulted"); 16 // Debug.LogException(exception); 17 } 18}

投稿2020/03/17 03:59

tamoto

総合スコア4103

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

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

fiveHundred

2020/03/17 07:17

横やりで失礼しますが、Unityのメインスレッド内(Update()やコルーチン内など)でこれを使っても大丈夫なのでしょうか? (これらはフレーム毎に終了もしくは中断させる必要があり、そうしないとその間フリーズしてしまいます)
tamoto

2020/03/17 08:04

Unity は専門ではないので確証はありませんが、 C# の言語仕様上では、非同期メソッドは await を境に処理をぶつ切りにするものなので、今回のケースでは GetValueAsync メソッドを呼び出した瞬間に制御が呼び出し元スレッドに返されるため、呼び出し元スレッドをフレームを跨ぐほどに長く掴むことにはならないはずです。 質問のコードで結果が 0 となっているということは、マルチスレッド処理自体は Unity 上でも仕様通りに動いているものと思います。
fiveHundred

2020/03/17 08:30

すいません。私が言いたいのは、「Update()内などでawaitを使っても大丈夫なのかどうか」です。 質問のコードだとawaitが無いため、非同期で処理されておりますが、awaitを使うと(私の認識では)処理が終わるまで待機することになるため、その待機中にフレームを跨いでしまうという懸念があるのですが、そのようにしても大丈夫なのでしょうか?
tamoto

2020/03/17 08:56 編集

> awaitを使うと(私の認識では)処理が終わるまで待機することになるため、 待機することにはならないので大丈夫です。 await を呼び出した箇所で、そこから先の処理の実行を待たずに Update() メソッドが終了した扱いになります。だから「非同期メソッド」なのです。 まあ、呼び出し先メソッドの実装次第では、長時間メインスレッドを潰すことも可能ではあるのですが。 今ググった程度の認識ですが、Unity はメインスレッドには SynchronizationContext を持つそうなので、await 後の処理は非同期呼び出しが完了し次第メインスレッドの空き時間にスケジュールされますね。ConfigureAwait(false) を利用することで続きの処理を完全に別コンテキストに流すこともできます。
fiveHundred

2020/03/17 10:00

ご説明の内容を元に、色々試して、納得いたしました。 どうも「Update()の呼び出し後は最後の行かreturn文までたどりつかないといけない」という固定概念が有ったので、そこで勘違いしていたみたいです。 ご説明ありがとうございました。 (ただ、色々試している際に、「非同期メソッドの実行後、エディタ上で停止させると、停止したのにも関わらず続きの処理が実行される(ver:2019.2.19f1)」といったことがあったので、使う人は一応気を付けたほうがいいかもしれません)
_Beginner

2020/03/19 13:51 編集

回答ありがとうございます。 GetIntOfInformation()の引数には欲しい値のキー名を入れるつもりでしたが、間違えて"name"や"id"を入れていました。 async Task<>、試してみようと思います。
_Beginner

2020/03/21 09:16

すみません。 やってみたのですがうまく呼び出されません。 voidメソッド内で Task<int> getScore = GetIntOfInformation("score"); Debug.Log(getScore.Result.ToString()); を実行しているのですがデバッグされません。 非同期処理は今まで全くやってこなかったので理解できていません... どうすれば呼び出せるのでしょうか。
_Beginner

2020/03/21 12:03

また、tamotoさんに教えていただいた一つ目のコードを試してみると i = await GetIntOfInformation(/* 何を渡す? */); の部分でUnity Editorがフリーズしてしまいます。
fiveHundred

2020/03/21 12:12

そのフリーズ中に他のスクリプトは動作していますか? (もし動作しているのであれば、GetIntOfInformation()の処理が終わってからawait以降の処理が行われるので、意図した動作…のはず)
tamoto

2020/03/21 12:24

何をやってるのか書いてないので知りませんが、Task の Result にアクセスすると非同期処理にならないので、デッドロックを起こしてフリーズすることはあるかもしれません。
_Beginner

2020/03/22 01:37 編集

エディターがフリーズするのは僕が間違っていました。 GetIntOfInformation()を呼び出すメソッドはasyncにしないといけないことに気が付きませんでした。 また、try/catchの文にしたら常に System.FormatException: Input string was not in a correct format. と、失敗が続きます。 例えば abcde/Information/Score の整数値をとりたい場合は回答内の2つ目のコードで path = "abcde", name = "Score" を代入しているのですが間違えているのでしょうか。
fiveHundred

2020/03/22 01:58

エラー文の発生箇所も記載してください。 それと、現状のコードがどうなっているか分からないので、記載したほうが分かりやすいと思います。
_Beginner

2020/03/22 14:20 編集

追記2に書きました。 調べてみたところこのエラー/例外は int i2 = int.Parse("1.5"); などの時に出るものらしいのですがそれらしい型の間違いところもないような気がします。
tamoto

2020/03/23 03:30

はい、それはつまり、今までも int.Parse で例外が出て動いてなかったことに気付いていなかったのが、書き換えによってようやく判明したという話ですね。 例えば "1.5" は明らかに int 型ではないので、int.Parse で読むことはできません。同様に、snapshot.ToString() の中身が何か int 型になりえない文字列だということでしょう。Log などに出力して確認してみれば良いです。
_Beginner

2020/03/23 08:20 編集

デバッグしてみたのですが、 Debug.Log(snapshot.ToString()); 結果は DataSnapshot { key = Score, value = 0 } と、数値は整数でした。 コンソール上でこの値を変更しても正しい数値が返ってきます。 これは"{ key = Score, value = 0 }"という文字列をパーズしようとしているのでしょうか。 valueの数値だけを取得するには何を使えばいいのでしょうか。
_Beginner

2020/03/23 08:27

Debug.Log(snapshot.GetValue(false).ToString()) これでうまくいきました。 ここまで教えていただいたお二人ありがとうございます。 勉強になりました。
_Beginner

2020/03/23 08:28

>>BluOxyさん ありがとうございます。 ちょうどコメント書いてて見えなかったのですが、教えていただきありがとうございます。 もっと細かくリファレンス見ようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問