vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【Unity】websocket-sharpのOnMessage

はじめに

前回作った WebSocket であれこれ試そうとしたら、 Unity で websocket-sharp を使って送信されたデータを受け取る、 OnMessage() でエラーが出たので調べた時のメモです。

なお Unity 側のコードはこれと同じです。

http://mslgt.hatenablog.com/entry/2017/05/11/073758

具体的には、OnMessage() が Main(UI) とは別の Thread から実行され、 Unity の処理(例: uGUI の Text に値を入れる、 Transform の値を変更する)を実行しようとするとエラーが発生していました。

WebSocketAccessor.cs

public class WebSocketAccessor : MonoBehaviour {

  public Text MessageText;
  private WebSocket ws;
    
    private void Start () {
    ws = new WebSocket("ws://localhost:5XXXXX/ws");
    
        // メッセージ受信時のイベント.
        ws.OnMessage += (sender, e) => {
          Debug.Log("Received " + e.Data);  


      // ここでエラー.
        MessageText.text = e.Data;

      
        };
        // 接続.
        ws.Connect ();
    }
    ~省略~

で、別 Thread から実行することが問題なのであれば、 Main Thread から実行するよう変更しましょう、というのが今回の内容です。

Main Threadで実行する

Java, Android では runOnUiThread というものがあります。

挙動は名前の通りなのですが、 C# ではこのようなものはなさそうです。

その代わりに、Main Thread で Context を取得しておき、それを通して Main Thread で実行する、ということが可能になります。

WebSocketAccessor.cs

public class WebSocketAccessor : MonoBehaviour {

  public Text MessageText;
  private WebSocket ws;
    
    private void Start () {
    ws = new WebSocket("ws://localhost:5XXXXX/ws");


    // Main ThreadのContextを取得する.
    var context = SynchronizationContext.Current;
    
    
        // メッセージ受信時のイベント.
        ws.OnMessage += (sender, e) => {
          Debug.Log("Received " + e.Data);  


      // Main Threadで実行する.
      context.Post(state => {
        MessageText.text = state.ToString();
      }, e.Data);

      
        };
        // 接続.
        ws.Connect ();
    }
    ~省略~
  • SynchronizationContext.Current を Main Thread で実行すると、 Main Thread の Context を取得することができます。

SynchronizationContext.Post()

SynchronizationContext.Post() から Main Thread での処理を呼び出すことができます。

この第一引数は SendOrPostCallback という delegate method で、第二引数は第一引数に渡す引数です。

この引数の型は Object なので、今回の string 以外のデータも渡すことができますが、 delegate method で型変換は必要です。

SynchronizationContext.Send()

もう一つ似たようなものに SynchronizationContext.Send() というものがあります。

パッと見同じに見えるのですが、どうやら Post() は非同期的に実行されるのに対し、 Send() は同期的に実行される、という違いがあるようです。

そのため、例えば下記の2つを比べると。。。

SynchronizationContext.Post()

// メッセージ受信時のイベント.
ws.OnMessage += (sender, e) => {
            
    Stopwatch watch = new Stopwatch();
    watch.Start();

    context.Post(state => {
    // 1000ミリ秒止める.
        Thread.Sleep(1000);
        MessageText.text = state.ToString();
    }, e.Data);
            
    Debug.Log("finish " + watch.ElapsedMilliseconds);
    watch.Stop();
};

SynchronizationContext.Send()

// メッセージ受信時のイベント.
ws.OnMessage += (sender, e) => {
            
    Stopwatch watch = new Stopwatch();
    watch.Start();

    context.Send(state => {
    // 1000ミリ秒止める.
        Thread.Sleep(1000);
        MessageText.text = state.ToString();
    }, e.Data);
            
    Debug.Log("finish " + watch.ElapsedMilliseconds);
    watch.Stop();
};

Post() は「finish 0」と出力されるのに対し、 Send() は「finish 1009」と出力されました。

なお、上記で Debug.Log("finish " + watch.ElapsedMilliseconds); が実行され、 Editor 上で表示されるタイミングは(おそらく MainThread で実行されるとかその辺の理由で)どちらも同じのため、注意が必要かもしません。

おわりに

以前 SpringBoot で動かしていた時には OnMessage も MainThread で受け取れていた気がしたのですが、今回変わった理由はまだよくわかっていません。

まぁ websocket-sharp 側で変更があったのかなぁ、というところではありますが。

次回も WebSocket(ASP.NET Core 側)で遊んでみる予定です。

参照