问题描述
我的问题是我尝试使用Unity套接字实现某些东西.每次收到新消息时,我都需要将其更新为updattext(这是Unity文本).但是,当我执行以下代码时,无效更新不会每次都调用.
My problem is I try to use Unity socket to implement something. Each time, when I get a new message I need to update it to the updattext (it is a Unity Text). However, When I do the following code, the void update does not calling every time.
我在空getInformation中不包含updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
的原因是此函数在线程中,当我在getInformation()中包含它时,它将出现错误:
The reason for I do not include updatetext.GetComponent<Text>().text = "From server: "+tempMesg;
in the void getInformation is this function is in the thread, when I include that in getInformation() it will come with an error:
我认为问题是我不知道如何在C#中同时运行主线程和子线程?也许还有其他问题.
I think the problem is I don't know how to run the main thread and the child thread in C# together? Or there maybe other problems.
这是我的代码:
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);
}
}
}
推荐答案
Unity不安全Thread
,因此他们决定通过添加一种在以下情况下引发异常的机制,使得无法从另一个Thread
调用其API:它的API是从另一个Thread
使用的.
Unity is not Thread
safe, so they decided to make it impossible to call their API from another Thread
by adding a mechanism to throw an exception when its API is used from another Thread
.
这个问题已经问了很多遍了,但是没有一个合适的解决方案/答案.答案通常是使用插件"或执行某些不是线程安全的操作.希望这将是最后一个.
This question has been asked so many times, but there have been no proper solution/answer to any of them. The answers are usually "use a plugin" or do something not thread-safe. Hopefully, this will be the last one.
您通常会在Stackoverflow或Unity论坛网站上看到的解决方案是简单地使用boolean
变量让主线程知道您需要在主Thread
中执行代码.这是不正确的,因为它不是线程安全的,也无法控制您提供要调用的函数.如果您有多个需要通知主线程的Threads
怎么办?
The solution you will usually see on Stackoverflow or Unity's forum website is to simply use a boolean
variable to let the main thread know that you need to execute code in the main Thread
. This is not right as it is not thread-safe and does not give you control to provide which function to call. What if you have multiple Threads
that need to notify the main thread?
您将看到的另一种解决方案是使用协程而不是Thread
.这不有效.对套接字使用协程不会更改任何内容.您仍然会遇到冻结问题.您必须坚持使用Thread
代码或使用Async
.
Another solution you will see is to use a coroutine instead of a Thread
. This does not work. Using coroutine for sockets will not change anything. You will still end up with your freezing problems. You must stick with your Thread
code or use Async
.
执行此操作的正确方法之一是创建一个集合,例如List
.当您需要在主线程中执行某些操作时,调用一个函数,该函数存储要在Action
中执行的代码.将Action
的List
复制到Action
的本地List
,然后从该List
中的本地Action
执行代码,然后清除该List
.这样可以避免其他Threads
不得不等待其完成执行.
One of the proper ways to do this is to create a collection such as List
. When you need something to be executed in the main Thread, call a function that stores the code to execute in an Action
. Copy that List
of Action
to a local List
of Action
then execute the code from the local Action
in that List
then clear that List
. This prevents other Threads
from having to wait for it to finish executing.
您还需要添加volatile boolean
,以通知Update
函数在List
中有等待执行的代码.将List
复制到本地List
时,应将其包装在lock
关键字周围,以防止另一个线程对其进行写入.
You also need to add a volatile boolean
to notify the Update
function that there is code waiting in the List
to be executed. When copying the List
to a local List
, that should be wrapped around the lock
keyword to prevent another Thread from writing to it.
执行我上面提到的内容的脚本:
A script that performs what I mentioned above:
UnityThread
脚本:
UnityThread
Script:
#define ENABLE_UPDATE_FUNCTION_CALLBACK
#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic;
public class UnityThread : MonoBehaviour
{
//our (singleton) instance
private static UnityThread instance = null;
////////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueUpdateFunc then executed from there
private static List<System.Action> actionQueuesUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesUpdateFunc to be executed
List<System.Action> actionCopiedQueueUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteUpdateFunc = true;
////////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueLateUpdateFunc then executed from there
private static List<System.Action> actionQueuesLateUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesLateUpdateFunc to be executed
List<System.Action> actionCopiedQueueLateUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteLateUpdateFunc = true;
////////////////////////////////////////////////FIXEDUPDATE IMPL////////////////////////////////////////////////////////
//Holds actions received from another Thread. Will be coped to actionCopiedQueueFixedUpdateFunc then executed from there
private static List<System.Action> actionQueuesFixedUpdateFunc = new List<Action>();
//holds Actions copied from actionQueuesFixedUpdateFunc to be executed
List<System.Action> actionCopiedQueueFixedUpdateFunc = new List<System.Action>();
// Used to know if whe have new Action function to execute. This prevents the use of the lock keyword every frame
private volatile static bool noActionQueueToExecuteFixedUpdateFunc = true;
//Used to initialize UnityThread. Call once before any function here
public static void initUnityThread(bool visible = false)
{
if (instance != null)
{
return;
}
if (Application.isPlaying)
{
// add an invisible game object to the scene
GameObject obj = new GameObject("MainThreadExecuter");
if (!visible)
{
obj.hideFlags = HideFlags.HideAndDontSave;
}
DontDestroyOnLoad(obj);
instance = obj.AddComponent<UnityThread>();
}
}
public void Awake()
{
DontDestroyOnLoad(gameObject);
}
//////////////////////////////////////////////COROUTINE IMPL//////////////////////////////////////////////////////
#if (ENABLE_UPDATE_FUNCTION_CALLBACK)
public static void executeCoroutine(IEnumerator action)
{
if (instance != null)
{
executeInUpdate(() => instance.StartCoroutine(action));
}
}
////////////////////////////////////////////UPDATE IMPL////////////////////////////////////////////////////
public static void executeInUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesUpdateFunc)
{
actionQueuesUpdateFunc.Add(action);
noActionQueueToExecuteUpdateFunc = false;
}
}
public void Update()
{
if (noActionQueueToExecuteUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueUpdateFunc queue
actionCopiedQueueUpdateFunc.Clear();
lock (actionQueuesUpdateFunc)
{
//Copy actionQueuesUpdateFunc to the actionCopiedQueueUpdateFunc variable
actionCopiedQueueUpdateFunc.AddRange(actionQueuesUpdateFunc);
//Now clear the actionQueuesUpdateFunc since we've done copying it
actionQueuesUpdateFunc.Clear();
noActionQueueToExecuteUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueUpdateFunc
for (int i = 0; i < actionCopiedQueueUpdateFunc.Count; i++)
{
actionCopiedQueueUpdateFunc[i].Invoke();
}
}
#endif
////////////////////////////////////////////LATEUPDATE IMPL////////////////////////////////////////////////////
#if (ENABLE_LATEUPDATE_FUNCTION_CALLBACK)
public static void executeInLateUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesLateUpdateFunc)
{
actionQueuesLateUpdateFunc.Add(action);
noActionQueueToExecuteLateUpdateFunc = false;
}
}
public void LateUpdate()
{
if (noActionQueueToExecuteLateUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueLateUpdateFunc queue
actionCopiedQueueLateUpdateFunc.Clear();
lock (actionQueuesLateUpdateFunc)
{
//Copy actionQueuesLateUpdateFunc to the actionCopiedQueueLateUpdateFunc variable
actionCopiedQueueLateUpdateFunc.AddRange(actionQueuesLateUpdateFunc);
//Now clear the actionQueuesLateUpdateFunc since we've done copying it
actionQueuesLateUpdateFunc.Clear();
noActionQueueToExecuteLateUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueLateUpdateFunc
for (int i = 0; i < actionCopiedQueueLateUpdateFunc.Count; i++)
{
actionCopiedQueueLateUpdateFunc[i].Invoke();
}
}
#endif
////////////////////////////////////////////FIXEDUPDATE IMPL//////////////////////////////////////////////////
#if (ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK)
public static void executeInFixedUpdate(System.Action action)
{
if (action == null)
{
throw new ArgumentNullException("action");
}
lock (actionQueuesFixedUpdateFunc)
{
actionQueuesFixedUpdateFunc.Add(action);
noActionQueueToExecuteFixedUpdateFunc = false;
}
}
public void FixedUpdate()
{
if (noActionQueueToExecuteFixedUpdateFunc)
{
return;
}
//Clear the old actions from the actionCopiedQueueFixedUpdateFunc queue
actionCopiedQueueFixedUpdateFunc.Clear();
lock (actionQueuesFixedUpdateFunc)
{
//Copy actionQueuesFixedUpdateFunc to the actionCopiedQueueFixedUpdateFunc variable
actionCopiedQueueFixedUpdateFunc.AddRange(actionQueuesFixedUpdateFunc);
//Now clear the actionQueuesFixedUpdateFunc since we've done copying it
actionQueuesFixedUpdateFunc.Clear();
noActionQueueToExecuteFixedUpdateFunc = true;
}
// Loop and execute the functions from the actionCopiedQueueFixedUpdateFunc
for (int i = 0; i < actionCopiedQueueFixedUpdateFunc.Count; i++)
{
actionCopiedQueueFixedUpdateFunc[i].Invoke();
}
}
#endif
public void OnDisable()
{
if (instance == this)
{
instance = null;
}
}
}
用法:
此实现允许您调用 3 最常用的Unity函数中的函数:Update
,LateUpdate
和FixedUpdate
函数.这也允许您在主Thread
中调用运行协程函数.它可以扩展为能够调用其他Unity回调函数中的函数,例如OnPreRender
和OnPostRender
.
This implementation allows you to call functions in the 3 most used Unity functions: Update
, LateUpdate
and FixedUpdate
functions. This also allows you call run a coroutine function in the main Thread
. It can be extended to be able to call functions in other Unity callback functions such as OnPreRender
and OnPostRender
.
1 .首先,通过Awake()
函数对其进行初始化.
1.First, initialize it from the Awake()
function.
void Awake()
{
UnityThread.initUnityThread();
}
2 .要从另一个线程在主Thread
中执行代码,请执行以下操作:
2.To execute a code in the main Thread
from another Thread:
UnityThread.executeInUpdate(() =>
{
transform.Rotate(new Vector3(0f, 90f, 0f));
});
这会将scipt所连接的当前对象旋转到90度.现在,您可以在另一个Thread
中使用Unity API(transform.Rotate
).
This will rotate the current Object the scipt is attached to, to 90 deg. You can now use Unity API(transform.Rotate
) in another Thread
.
3 .要从另一个线程调用主Thread
中的函数:
3.To call a function in the main Thread
from another Thread:
Action rot = Rotate;
UnityThread.executeInUpdate(rot);
void Rotate()
{
transform.Rotate(new Vector3(0f, 90f, 0f));
}
#2 和#3 示例在 Update
函数.
The #2 and #3 samples executes in the Update
function.
4 .要在 LateUpdate
中执行代码来自另一个线程的功能:
4.To execute a code in the LateUpdate
function from another Thread:
例如摄像机跟踪代码.
UnityThread.executeInLateUpdate(()=>
{
//Your code camera moving code
});
5 .要在 FixedUpdate
中执行代码来自另一个线程的功能:
5.To execute a code in the FixedUpdate
function from another Thread:
在进行物理操作(例如对Rigidbody
施加力)时的示例.
Example of this when doing physics stuff such as adding force to Rigidbody
.
UnityThread.executeInFixedUpdate(()=>
{
//Your code physics code
});
6 .要从另一个线程在主Thread
中启动协程函数:
6.To Start a coroutine function in the main Thread
from another Thread:
UnityThread.executeCoroutine(myCoroutine());
IEnumerator myCoroutine()
{
Debug.Log("Hello");
yield return new WaitForSeconds(2f);
Debug.Log("Test");
}
最后,如果您不需要执行 LateUpdate
和 FixedUpdate
函数,您应该在下面的这段代码中注释两行:
Finally, if you don't need to execute anything in the LateUpdate
and FixedUpdate
functions, you should comment both lines of this code below:
//#define ENABLE_LATEUPDATE_FUNCTION_CALLBACK
//#define ENABLE_FIXEDUPDATE_FUNCTION_CALLBACK
这将提高性能.
这篇关于从另一个线程使用Unity API或在主线程中调用函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!