获取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 }