web-dev-qa-db-ja.com

別のスレッドからUnity APIを使用するか、メインスレッドで関数を呼び出します

私の問題は、Unityソケットを使用して何かを実装しようとしていることです。毎回、新しいメッセージを受け取ったら、それを更新テキスト(Unity Text)に更新する必要があります。ただし、次のコードを実行すると、void updateが毎回呼び出されません。

updatetext.GetComponent<Text>().text = "From server: "+tempMesg;をvoid getInformationに含めない理由は、この関数がスレッドにあるためです。getInformation()に含めるとエラーが発生します。

getcomponentfastpath can only be called from the main thread

問題は、C#でメインスレッドと子スレッドを一緒に実行する方法がわからないことだと思いますか?または、他の問題があるかもしれません。

ここに私のコードがあります:

using UnityEngine;
using System.Collections;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine.UI;


public class Client : MonoBehaviour {

    System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
    private Thread oThread;

//  for UI update
    public GameObject updatetext;
    String tempMesg = "Waiting...";

    // Use this for initialization
    void Start () {
        updatetext.GetComponent<Text>().text = "Waiting...";
        clientSocket.Connect("10.132.198.29", 8888);
        oThread = new Thread (new ThreadStart (getInformation));
        oThread.Start ();
        Debug.Log ("Running the client");
    }

    // Update is called once per frame
    void Update () {
        updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
        Debug.Log (tempMesg);
    }

    void getInformation(){
        while (true) {
            try {
                NetworkStream networkStream = clientSocket.GetStream ();
                byte[] bytesFrom = new byte[10025];
                networkStream.Read (bytesFrom, 0, (int)bytesFrom.Length);
                string dataFromClient = System.Text.Encoding.ASCII.GetString (bytesFrom);
                dataFromClient = dataFromClient.Substring (0, dataFromClient.IndexOf ("$"));
                Debug.Log (" >> Data from Server - " + dataFromClient);

                tempMesg = dataFromClient;

                string serverResponse = "Last Message from Server" + dataFromClient;

                Byte[] sendBytes = Encoding.ASCII.GetBytes (serverResponse);
                networkStream.Write (sendBytes, 0, sendBytes.Length);
                networkStream.Flush ();
                Debug.Log (" >> " + serverResponse);

            } catch (Exception ex) {
                Debug.Log ("Exception error:" + ex.ToString ());
                oThread.Abort ();
                oThread.Join ();
            }
//          Thread.Sleep (500);
        }
    }
}
24
user6142261

Unityのスレッドに関する記述の多くは、実際にはまったく間違っています。

どうして?

基本的な事実は、Unityがもちろん完全にフレームベースであることです。

フレームベースのシステムで作業する場合、スレッドの問題は従来のシステムとは著しく異なります。

それはまったく異なるパラダイムです。

(実際、多くの点ではるかに簡単です。)

フレームベースのシステムでのスレッドの問題は、従来のシステムとは完全に完全に異なります。 (ある意味、フレームベースのシステムには特定の概念が存在しないため、扱いやすい

何らかの値を表示するUnity温度計ディスプレイがあるとします

Thermo.cs

enter image description here

そのため、Updateで呼び出される関数があります。

func void ShowThermoValue(float fraction) {
   display code, animates a thermometer
}

これはフレームごとに1回だけ実行され、それだけです。

基本的な事実を忘れるのは非常に簡単です

もちろん、「メインスレッド」でのみ実行されます。

(Unityには他に何もありません!ただ...............「Unityスレッド」があります!)

しかし、心に留めておくべき重要なパラダイムは次のとおりです。フレームごとに実行されます

さて、他のどこかで、Thermo.csには、「新しい値が到着しました」という概念を処理する関数があります。

[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {

    ... ???
}

もちろん、これはクラス関数であることに注意してください!

ShowThermoValueなどの通常のUnity関数に「到達」することはできません!!!!!! 脚注1

それは完全に、完全に、意味のない概念です。 ShowThermoValueに「到達」することはできません。

Unityで「スレッド」に「到達」しようとする「スレッド関連」のコード例はすべて、完全かつ完全に誤誘導されています

到達するスレッドはありません!

繰り返しになりますが、これは驚くべきことです。Unityのスレッドに関するwwwのすべての記事をグーグルで検索しただけでも、記事や投稿の多くは概念レベルではまったく間違っています。

たとえば、値は非常に頻繁に不規則に到着します。

PCやiPhoneのラックにさまざまな科学デバイスを接続して、新しい「温度」値が非常に頻繁に到着する可能性があります....フレームあたり数十回..または、おそらく数秒ごとにのみ。

それで、あなたは値をどうしますか?

これ以上簡単ではありません。

到着値スレッドから、あなたがすることは.............それを待つだけです.............コンポーネントに変数を設定します!!

WTF?あなたがすることは、変数を設定するだけです?それでおしまい?どうしてそんなに簡単なのでしょうか?

これは、これらの異常な状況の1つです。

  1. Unityのスレッドに関する記述の多くは、まったく間違っていますです。 「1つのエラー」や「修正が必要なもの」があるわけではありません。基本的なパラダイムレベルでの「完全かつ完全に間違っている」だけです。

  2. 驚くべきことに、実際のアプローチはとてつもなく簡単です。

  3. とても簡単なので、何か間違ったことをしていると思うかもしれません。

だから変数がある...

[System.Nonserialized] public float latestValue;

「到着スレッド」から設定して......

[MonoPInvokeCallback(typeof(ipDel))]
public static void NewValueArrives(float f) {

    ThisScript.runningInstance.latestValue = f; // done
}

正直それだけです。

基本的に、「Unityでのスレッド化」の世界最大の専門家であるためには、明らかにフレームベースであるため、上記以外にやることはありません。

そして、ShowThermoValueが各フレームと呼ばれるときはいつでも......その値を表示するだけです!

本当に、それだけです!

[System.Nonserialized] public float latestValue;
func void ShowThermoValue() { // note NO arguments here!
   display code, animates a thermometer
   thermo height = latestValue
}

単に「最新の」値を表示しています。

latestValueは、そのフレームを1回、2回、10回、または100回設定した可能性があります............しかし、単に表示する場合、ShowThermoValueがそのフレームを実行するときの値は何でも!

他に何を表示できますか?

温度計は画面上で60fpsで更新されます。 脚注2

実に簡単です。簡単です。驚くべきことですが、本当です。


(重要なことは別として、vector3などはUnity/C#ではアトミックではないことを忘れないでください)

ユーザー@dymanoidが指摘しているように(以下の重要な議論を読んでください)、Unity/C#環境ではfloatはアトミックですが、他のもの(たとえば、Vector3など)IS NOT ATOMIC 。通常(ここの例のように)、ネイティブプラグインなどの計算からのフロートのみを渡します。ただし、ベクトルなどはアトミックではないことに注意することが不可欠です


フレームベースのシステムでは、レーストラックやロックの問題によって引き起こされる問題のほとんどが概念的に存在しないため、フレームベースのシステムと結びつくことがあります。

フレームベースのシステムでは、ゲームアイテムは、どこかに設定された「現在の値」に基づいて表示または動作する必要があります。他のスレッドからの情報がある場合は、それらの値を設定するだけです-done

あなたは意味のないことはできません "メインスレッドと通信"Unityのメインスレッド.............はフレームベースであるためです!

スレッド化の問題はさておき、連続システムは、フレームパラダイムシステムとに有意義に対話することはできません。

ロック、ブロッキング、レーストラックの問題のほとんどは、フレームベースのパラダイムではnon-existentです。latestValueが10回設定されている場合、1つの特定のフレームで、100万回、10億回..あなたは何ができますか? .. 1つの値しか表示できません!

昔ながらのプラスチックフィルムを考えてください。あなたは文字通り......フレームを持っています、そしてそれだけです。 1つの特定のフレームでlatestValueを1兆回設定すると、ShowThermoValueは実行時に取得した1つの値を(その60秒間)表示するだけです。

あなたがすることは、情報をどこかに残すことです。フレームパラダイムシステムは、必要に応じてそのフレームを使用します。

これで簡単です。

したがって、ほとんどの「スレッドの問題」はUnityで消えます

あなたができることはすべてから

  • 他の計算スレッドまたは

  • プラグインスレッドから、

ゲームで使用できる「ドロップオフ値」です。

それでおしまい!


脚注


1 どうして?思考実験として、あなたが別のスレッドにいるという問題を忘れてください。 ShowThermoValueは、フレームエンジンによってフレームごとに1回実行されます。意味のある方法で「呼び出す」ことはできません。通常のOOソフトウェアとは異なり、たとえば、クラスのインスタンス(コンポーネント??意味のない)をインスタンス化して、その関数を実行することはできません-それは完全に意味がありません。

「通常の」スレッドプログラミングでは、スレッドがやり取りすることがあります。そのため、ロックや競馬場などについて懸念があります。しかし、それはすべて、ECS、フレームベースのシステムではmeaninglessです。 「話す」ことは何もありません。

Unityが実際にマルチスレッド化されたとしましょう!!!!したがって、Unityのメンバーはすべてのエンジンをマルチスレッド方式で実行しています。 違いはありません-意味のある方法でShowThermoValueを「取得」することはできません!フレームエンジンがフレームで1回実行するコンポーネントです。

したがって、NewValueArrivesはどこにもありません-クラス関数です!

見出しの質問に答えましょう:

「別のスレッドからUnity APIを使用するか、メインスレッドで関数を呼び出しますか?」

概念は>>完全に無意味です<<。 Unity(すべてのゲームエンジンと同様)はフレームベースです。メインスレッドで関数を「呼び出す」という概念はありません。類推すると:セルロイド映画時代の撮影監督が、フレームの1つを実際に移動する方法を尋ねるようなものです。

enter image description here

もちろん、それは無意味です。できることは、次の写真、次のフレームのために何かを変更することだけです。


2 私は「到着値スレッド」を参照します...実際に! NewValueArrivesは、メインスレッドで実行される場合とされない場合があります!!!!プラグインのスレッド、または他のスレッドで実行できます! NewValueArrives呼び出しを処理するまでに、実際には完全にシングルスレッドになる場合があります。 それは問題ではありません!フレームベースのパラダイムでは、ShowThermoValueなどのコンポーネントが使用する可能性のある情報を「置き去りに」するだけです、彼らは合うと思う。

6
Fattie

私はこの問題に対するこのソリューションを使用しています。このコードを使用してスクリプトを作成し、ゲームオブジェクトに添付します。

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using UnityEngine;

public class ExecuteOnMainThread : MonoBehaviour {

    public readonly static ConcurrentQueue<Action> RunOnMainThread = new ConcurrentQueue<Action>();

    void Update()
    {
        if(!RunOnMainThread.IsEmpty())
        {
           while(RunOnMainThread.TryDequeue(out action))
           {
             action.Invoke();
           }
        }
    }

}

次に、メインスレッドで何かを呼び出し、アプリケーションの他の関数からUnity APIにアクセスする必要がある場合:

ExecuteOnMainThread.RunOnMainThread.Enqueue(() => {

    // Code here will be called in the main thread...

});
3
Pelayo Méndez