在使用Unity进行Socket通信的时候使用了线程,在接收到消息后再创建物体,使用了Resource.Load,发生了错误:
UnityEngine.UnityException: Load can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
也就是说子线程不能调用Unity的方法,要在主线程执行。
找了几个解决方案,都是返回给主线程去执行方法。
Unity关于有些代码不能在除主线程外的线程使用的问题
https://blog.csdn.net/qq_34818497/article/details/79464793
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace LX.Common.Threading {
/// <summary>
/// 线程调度,将其它线程中的代码放置在主线程中执行
/// </summary>
public sealed class Dispatcher : MonoBehaviour {
private static Dispatcher instance;
private int _lock;
private bool _isRun;
private Queue<Action> _queWaitAction = new Queue<Action>();
private Queue<Action> _queExecute;
public static void Run(Action action) {
instance.Runner(action);
}
private void Runner(Action action) {
while (true) {
//以原子操作的形式,将 32 位有符号整数设置为指定的值并返回原始值。
if (0 == Interlocked.Exchange(ref _lock, 1)) {
//acquire lock
_queWaitAction.Enqueue(action);
_isRun = true;
//exist
Interlocked.Exchange(ref _lock, 0);
break;
}
}
}
private void Awake() {
instance = this;
DontDestroyOnLoad(this.gameObject);
}
private void Update() {
if (_isRun) {
_queExecute = null;
//主线程不推荐使用lock关键字,防止block 线程,以至于deadlock
if (0 == Interlocked.Exchange(ref _lock, 1)) {
_queExecute = new Queue<Action>(_queWaitAction.Count);
while (_queWaitAction.Count != 0) {
_queExecute.Enqueue(_queWaitAction.Dequeue());
}
//finished
_isRun = false;
//release
Interlocked.Exchange(ref _lock, 0);
}
//not block
if (_queExecute != null) {
while (_queExecute.Count != 0) {
_queExecute.Dequeue()();
}
}
}
}
}
}
//用法: LX.Common.Threading.Dispatcher.Run(() => {/*函数体*/})
改写:
调用的脚本把class里面的替换进去,包括改写的Awake和Update方法,修改原本的类名变量Dispatcher
完整代码,Unity是服务端:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System;
public class Socket_server : MonoBehaviour
{
private static Socket_server _current;
// Start is called before the first frame update
void Start()
{
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// 服务器监听消息
/// </summary>
void Listen()
{
try
{
//建立udp服务器,参数2:udp协议以数据报的方式传输,参数3:UDP协议
Socket udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//为服务器绑定IP
string ip_ = Socket_GetLocalIp();
Debug.Log(ip_);
IPAddress ip = IPAddress.Parse(ip_);
EndPoint ep = new IPEndPoint(ip, 3457);
udpServer.Bind(ep);
//接收数据
EndPoint endP = new IPEndPoint(IPAddress.Any, 0);
string message;
byte[] data = new byte[1024];
int length = 0;
//把数据的来源放到第二个参数上
while (true)
{
length = udpServer.ReceiveFrom(data, ref endP);
message = Encoding.UTF8.GetString(data, 0, length);
Debug.Log("从IP:" + (endP as IPEndPoint).Address + "取到了消息:" + message);
Socket_server.Run(() => { makeSun(); });
}
}
catch(System.Exception e)
{
Debug.LogError(e);
throw;
}
}
public void makeSun()
{
//生成动画
Image sun;
Image obj_sun = Resources.Load<Image>("sun 1");
sun = GameObject.Instantiate(obj_sun);
sun.GetComponent<Transform>().SetParent(GameObject.Find("Canvas").GetComponent<Transform>(), true);
}
/// <summary>
/// 获取本地的IP地址
/// </summary>
/// <returns></returns>
public string Socket_GetLocalIp()
{
string AddressIP = string.Empty;
foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
{
AddressIP = _IPAddress.ToString();
}
}
return AddressIP;
}
private static Socket_server instance;
private int _lock;
private bool _isRun;
private Queue<Action> _queWaitAction = new Queue<Action>();
private Queue<Action> _queExecute;
public static void Run(Action action)
{
instance.Runner(action);
}
private void Runner(Action action)
{
while (true)
{
//以原子操作的形式,将 32 位有符号整数设置为指定的值并返回原始值。
if (0 == Interlocked.Exchange(ref _lock, 1))
{
//acquire lock
_queWaitAction.Enqueue(action);
_isRun = true;
//exist
Interlocked.Exchange(ref _lock, 0);
break;
}
}
}
private void Awake()
{
instance = this;
DontDestroyOnLoad(this.gameObject);
}
private void Update()
{
if (_isRun)
{
_queExecute = null;
//主线程不推荐使用lock关键字,防止block 线程,以至于deadlock
if (0 == Interlocked.Exchange(ref _lock, 1))
{
_queExecute = new Queue<Action>(_queWaitAction.Count);
while (_queWaitAction.Count != 0)
{
_queExecute.Enqueue(_queWaitAction.Dequeue());
}
//finished
_isRun = false;
//release
Interlocked.Exchange(ref _lock, 0);
}
//not block
if (_queExecute != null)
{
while (_queExecute.Count != 0)
{
_queExecute.Dequeue()();
}
}
}
}
}
客户端WindowsForm窗口,udp发送消息:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.Sockets;
using System.Net;
namespace WindowsFormsApp1_socket1_client
{
public partial class Form1 : Form
{
private Socket udpClient;//创建客户端
private IPAddress ip;
private EndPoint ep;
private byte[] data = new byte[2048];//用来接收和发送数据
private int length = 0;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Socket_SendMessageToServer();
}
private void Form1_Load(object sender, EventArgs e)
{
Socket_UDPInit();
}
public void Socket_SendMessageToServer()
{
string message;
Console.Write("Send Message");
message = "tset";
data = Encoding.UTF8.GetBytes(message);
try
{
int returnnum = udpClient.SendTo(data, ep);
}
catch (Exception e)
{
Console.Write(e);
throw;
}
}
public string Socket_GetLocalIp()
{
///获取本地的IP地址
string AddressIP = string.Empty;
foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
{
if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
{
AddressIP = _IPAddress.ToString();
}
}
return AddressIP;
}
private void Socket_UDPInit()
{
//建立udp客户端,参数2:udp协议以数据报的方式传输,参数3:UDP协议
udpClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//发送信息准备工作
String a = Socket_GetLocalIp();
ip = IPAddress.Parse(a);
ep = new IPEndPoint(ip, 3457);
}
}
}