Search Unity

  1. Unity 2020.2 has been released.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

WPF messaging to Unity via M2MQTT - Error : Create can only be called from the main thread

Discussion in 'Scripting' started by G_B_A, Feb 13, 2018.

  1. G_B_A

    G_B_A

    Joined:
    Feb 2, 2016
    Posts:
    6
    Hi everyone, I rarely post in forums but this is something I can't wrap my head around.
    I'm trying to send a string path from a WPF application to Unity using MQTT. I'm using this library https://github.com/vovacooper/Unity3d_MQTT . So far I can get my path to Unity, that's fine. However, when I try to call a method to open the path I receive, I get this error:
    Here's my code

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Net;
    4. using uPLibrary.Networking.M2Mqtt;
    5. using uPLibrary.Networking.M2Mqtt.Messages;
    6. using System.Text;
    7.  
    8. public class SimpleMQTT : MonoBehaviour
    9. {
    10.  
    11.     // Host IP adress
    12.     public string host;
    13.     // Port number - 1883 by default for easy test
    14.     public int port;
    15.     //Is it secured or not
    16.     public bool secured;
    17.     //The topics you want to receive
    18.     public string[] topicToSubscribe;
    19.  
    20.     //Reference to MQTT client
    21.     private MqttClient client;
    22.  
    23.     //Debug variables
    24.     private string publishTopic = "/Test";
    25.     private string publishMessage = "This is a test from Unity";
    26.  
    27.     AudioFetch audioFetch;
    28.  
    29.     // Use this for initialization
    30.     void Start()
    31.     {
    32.         audioFetch = GameObject.FindObjectOfType<AudioFetch>();
    33.         //Connect
    34.         Connect();
    35.     }
    36.  
    37.     void Connect()
    38.     {
    39.         // create client instance
    40.         client = new MqttClient(IPAddress.Parse(host), port, secured, null);
    41.  
    42.         //Register
    43.         Register();
    44.     }
    45.  
    46.     void Register()
    47.     {
    48.         // register to message received
    49.         client.MqttMsgPublishReceived += Client_MqttMsgPublishReceived;
    50.         string clientId = Guid.NewGuid().ToString();
    51.         client.Connect(clientId);
    52.  
    53.         //Subscribe
    54.         Subscribe();
    55.     }
    56.  
    57.  
    58.     private void Subscribe()
    59.     {
    60.         //Susbscribre to the topic specified // None by default
    61.         for (int i = 0; i < topicToSubscribe.Length; i++)
    62.         {
    63.             client.Subscribe(new string[] { topicToSubscribe[i] }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
    64.         }
    65.     }
    66.  
    67.     void Client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
    68.     {
    69.         Debug.Log(Encoding.UTF8.GetString(e.Message));
    70.         audioFetch.GetAudio(Encoding.UTF8.GetString(e.Message));
    71.     }
    72.  
    73.     //for debug purpose // will send message
    74.  
    75.     void OnGUI()
    76.     {
    77.         if (GUI.Button(new Rect(20, 40, 80, 20), "Level 1"))
    78.         {
    79.             Debug.Log("Sending: " + publishMessage);
    80.             Publish();
    81.         }
    82.     }
    83.  
    84.     void Publish()
    85.     {
    86.         client.Publish(publishTopic, Encoding.UTF8.GetBytes(publishMessage), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
    87.     }
    88. }
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class AudioFetch : MonoBehaviour {
    6.  
    7.     AudioSource audioSource;
    8.     WWW www;
    9.  
    10.     // Use this for initialization
    11.     void Start () {
    12.         audioSource = gameObject.GetComponent<AudioSource>();
    13.     }
    14.  
    15.     public void GetAudio(string path)
    16.     {
    17.         www = new WWW(path);
    18.         audioSource.clip = www.GetAudioClip();
    19.         audioSource.Play();
    20.     }
    21. }
     
  2. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,767
    The message says what happens.
    Some Unity API call has been made on a different thread, probably due to the callback you've registered for the 'MqttMsgPublishReceived' event.

    The library you're using is not designed with Unity in mind, so you have to either avoid Unity API calls or dispatch / delay those in order to run that logic on the main thread.

    So, in your case the following happens:
    Data is received asynchronously and the 'MqttMsgPublishReceived' event will be raised on a seperate thread. Thus your subscribed method will be executed on this thread as well. That method attempts to access the audioFetch component by calling GetAudio, which is still okay... Technically you can access components and their members as long as no call to the Unity API is involved. There are some exceptions to this, such as Debug.Log, a few Utilities, some value types and a few other types.

    However, WWW is apparently none of those, so that's where it starts to fail when you create the WWW instance. This is part of the Unity API which is designed to only allow calls from the main thread.
     
  3. G_B_A

    G_B_A

    Joined:
    Feb 2, 2016
    Posts:
    6
    Yes, I mean I kind of understand the error really. I guess my question was more, how can I run my other method on the main thread, instead of on the same thread I receive the message.
     
  4. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,767
    You can write a dispatcher that'll be triggered on the main thread, e.g. every n-th frame or every n seconds.
    The dispatcher can either be event-based or interface based. You can then add to it from another thread and trigger the execution from the main thread and reset the list. All the operations should be implemented with thread-safety.
     
  5. G_B_A

    G_B_A

    Joined:
    Feb 2, 2016
    Posts:
    6
    Thank you for that pointer. I ended up using the dispatcher https://github.com/PimDeWitte/UnityMainThreadDispatcher and it works fine. In the mean time I also did a TCP communication system. It uses threads as well but it is possible to run a method on the main thread by using a bool in the update and switching it's value when I receive data. I will still use the dispatcher as it is cleaner and MQTT gives me options to connect different kinds of devices.
     
unityunity