我正在尝试将LAN服务器发现添加到我的Unity游戏中。我正在使用UDP来广播请求。如果网络上的任何人都是服务器,他们将回复一些服务器数据。我已经弄清楚了这一部分,但我需要使用一些Unity函数(包括其他自定义monobehaviour脚本)来生成数据包数据。
Unity不允许从主要线程以外的任何线程访问其API。
我正在使用UdpClient.BeginReceive,UdpClient.EndReceive,AsyncCallback流程。因此接收AsyncCallback函数(下面示例脚本中的AsyncRequestReceiveData
)在另一个不允许我使用任何Unity函数的线程中运行。
我使用this answer作为我的剧本的基础。
当脚本听到请求时,我尝试在AsyncRequestReceiveData
中触发委托和事件,但它似乎在同一个单独的线程中触发。
由单独的线程在主线程中触发的事件会很有效,但我不确定如何做到这一点。
虽然脚本继承自MonoBehaviour
(Unity 3D中的基础对象),但它应该运行得很好。
脚本的输出应如下所示:
发送请求:requestedServers
请求收到:requesServers
以下是准系统脚本:
using UnityEngine;
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
public class TestUDP : MonoBehaviour {
public delegate void RequestReceivedEventHandler(string message);
public event RequestReceivedEventHandler OnRequestReceived;
// Use this to trigger the event
protected virtual void ThisRequestReceived(string message)
{
RequestReceivedEventHandler handler = OnRequestReceived;
if(handler != null)
{
handler(message);
}
}
public int requestPort = 55795;
UdpClient udpRequestSender;
UdpClient udpRequestReceiver;
// Use this for initialization
void Start () {
// Set up the sender for requests
this.udpRequestSender = new UdpClient();
IPEndPoint requestGroupEP = new IPEndPoint(IPAddress.Broadcast, this.requestPort);
this.udpRequestSender.Connect(requestGroupEP);
// Set up the receiver for the requests
// Listen for anyone looking for us
this.udpRequestReceiver = new UdpClient(this.requestPort);
this.udpRequestReceiver.BeginReceive(new AsyncCallback(AsyncRequestReceiveData), null);
// Listen for the request
this.OnRequestReceived += (message) => {
Debug.Log("Request Received: " + message);
// Do some more stuff when we get a request
// Use `Network.maxConnections` for example
};
// Send out the request
this.SendRequest();
}
void Update () {
}
void SendRequest()
{
try
{
string message = "requestingServers";
if (message != "")
{
Debug.Log("Sendering Request: " + message);
this.udpRequestSender.Send(System.Text.Encoding.ASCII.GetBytes(message), message.Length);
}
}
catch (ObjectDisposedException e)
{
Debug.LogWarning("Trying to send data on already disposed UdpCleint: " + e);
return;
}
}
void AsyncRequestReceiveData(IAsyncResult result)
{
IPEndPoint receiveIPGroup = new IPEndPoint(IPAddress.Any, this.requestPort);
byte[] received;
if (this.udpRequestReceiver != null) {
received = this.udpRequestReceiver.EndReceive(result, ref receiveIPGroup);
} else {
return;
}
this.udpRequestReceiver.BeginReceive (new AsyncCallback(AsyncRequestReceiveData), null);
string receivedString = System.Text.Encoding.ASCII.GetString(received);
// Fire the event
this.ThisRequestReceived(receivedString);
}
}
我见过this question (Cleanest Way to Invoke Cross-Thread Events),但我似乎无法弄清楚如何运作以及如何融入。
答案 0 :(得分:2)
解决方案的工作原理是跟踪需要在主线程中执行的任务列表(队列),然后通过Update()
在HandleTasks()
中运行该队列;执行并从队列中删除每个。您可以通过调用QueueOnMainThread()
并传递匿名函数来添加到任务队列。
我通过this answer
从Pragmateek获得了一些灵感您可以使用单独的脚本来管理主线程任务,或者只将其合并到您现有的脚本中,您需要在主线程中运行某些内容。
以下是问题中我原始代码段的full source固定解决方案。
以下是排队和运行的必要位:
// We use this to keep tasks needed to run in the main thread
private static readonly Queue<Action> tasks = new Queue<Action>();
void Update () {
this.HandleTasks();
}
void HandleTasks() {
while (tasks.Count > 0)
{
Action task = null;
lock (tasks)
{
if (tasks.Count > 0)
{
task = tasks.Dequeue();
}
}
task();
}
}
public void QueueOnMainThread(Action task)
{
lock (tasks)
{
tasks.Enqueue(task);
}
}
要在主线程上排队,只需传递一个lambda表达式:
this.QueueOnMainThread(() => {
// We are now in the main thread...
Debug.Log("maxConnections: " + Network.maxConnections);
});
如果您使用的是WinForms,this article: Avoiding InvokeRequired有一些很好的编码模式。
答案 1 :(得分:-1)
此代码示例是否适合您?可以从任何线程调用X.
//place in class where main thread object is
void X(int sampleVariable)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) (() => X(sampleVariable)));
return;
}
// write main thread code here
}