获取UdpClient在主线程中接收数据
我正在尝试将LAN服务器发现添加到我的Unity游戏中。 我正在使用UDP来广播请求。 如果网络上的任何人是服务器,他们将回复一些服务器数据。 我已经弄清楚了这一部分,但我需要使用一些Unity函数(包括其他自定义monobehaviour脚本)来生成数据包数据。
Unity不允许从主要线程以外的任何线程访问其API。
我正在使用UdpClient.BeginReceive , UdpClient.EndReceive , AsyncCallback流程。 因此接收AsyncCallback函数( AsyncRequestReceiveData
的示例脚本中的AsyncRequestReceiveData
)在另一个不允许我使用任何Unity函数的线程中运行。
我用flamy的这个答案作为我脚本的基础。
我已经尝试在脚本听到请求时在AsyncRequestReceiveData
触发委托和事件,但它似乎在同一个单独的线程中触发。
由单独的线程在主线程中触发的事件可以很好地工作,但我不确定如何做到这一点。
虽然脚本inheritance自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); } }
我已经看到了这个问题( 最简洁的方式来调用跨线程事件 ),但我似乎无法弄清楚如何工作以及如何融入。
该解决方案的工作原理是跟踪需要在主线程中执行的任务列表(队列),然后通过HandleTasks()
在Update()
运行该队列。 执行并从队列中删除每个。 您可以通过调用QueueOnMainThread()
并传递匿名函数来添加到任务队列。
我从Pragmateek的 这个答案中得到了一些灵感
您可以使用单独的脚本来管理主线程任务,或者只将其合并到您现有的脚本中,您需要在主线程中运行某些内容。
以下是问题中原始片段的完整源代码 ,固定解决方案。
下面是排队和运行的必要位:
// We use this to keep tasks needed to run in the main thread private static readonly Queue tasks = new Queue (); 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,那么本文: 避免使用InvokeRequired有一些很好的编码模式。
此代码示例是否适合您? 可以从任何线程调用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 }