我有一个代码片段,其功能类似于Unity中的Grpc客户端。该代码样式是为控制台应用程序设计的,可以在Main方法中调用它,阻止它并始终接收数据。现在,我想在Unity中使用它,显然我希望我的应用程序同时在Unity中运行。另外,我的最终目标是拥有像Udp Client这样的产品。只需调用一次,它就会一直为您接收数据,而不会阻塞宿主应用程序的任何部分。
此设计最重要的部分是,如果有任何事件,我将得到更新,如果没有新事件,则我将不接收任何数据。它全部发生在 ObserveEvents(channel).Wait(); 中。问题是 Wait(); 。一直保持主线程,工作线程在监听更新。在“播放”模式下,Unity不再响应!
我可以绕开它说,我不需要这样的设计,我可以每隔一秒钟或每隔几帧接收事件。这样,我有了我的Unity应用程序,但是我丢失了很多帧速率,无论我的数据没有流畅地流入Unity中的主机应用程序。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using UnityEngine;
namespace Scripts
{
public class GrpcChannel
{
public void GrpcServer(string ip, int port)
{
var channel = new Channel(ip, port, ChannelCredentials.Insecure);
ObserveEvents(channel).Wait();
channel.ShutdownAsync().Wait();
}
private async Task ObserveEvents(Channel channel)
{
Debug.Log("Observing events...");
var eventService = new EventService.EventServiceClient(channel);
var request = new RegisterOnEvent();
using (var call = eventService.OnEvent(request))
{
var responseStream = call.ResponseStream;
while (await responseStream.MoveNext())
{
//var receivedEvent = responseStream.Current;
// on change of any data, this method will be fired
GetJointPosition(channel, "Flower_id_22134");
}
}
}
private void GetJointPosition(Channel channel, string flowerName)
{
var client = new JointPositionService.JointPositionServiceClient(channel);
var request = new GetJointPositionRequest
{
FlowerName = flowerName
};
var response = client.GetJointPositionAsync(request);
SavePositions(response.ResponseAsync.Result.Positions);
}
private void SavePositions(IEnumerable<JointPosition> positions)
{
var jointPositions = positions.ToList();
for (var i = 0; i < Instance.Ref.AxesValues.Length; i++)
{
var axeValueDegree = jointPositions[i].Value * 180 / Math.PI;
Instance.Ref.AxesValues[i] = (float)axeValueDegree;
}
}
}
}
我这样称呼它:
var grpcChannel = new GrpcChannel();
grpcChannel.GrpcServer("192.168.123.16", 30201);
在Update()方法中。不幸的是,它在Start()方法中不起作用。是的,显然,每个框架都需要创建一个新频道,否则它将无法正常工作。
当前的实现是这样的,它每7帧调用一次,而无需使用特殊的等待事件设计:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using TMPro;
using UnityEngine;
namespace Assets.Scripts
{
public class AxisValuesService : MonoBehaviour
{
public TextMeshProUGUI[] AxesValuesTexts;
[HideInInspector] public Dictionary<uint, float> AxisValues = new Dictionary<uint, float>();
[HideInInspector] private int counter = 0;
private void Update()
{
counter++;
if (counter == 7)
{
try
{
var channel = new Channel("192.168.123.16", 30201, ChannelCredentials.Insecure);
GetJointPosition(channel, "Flower_id_22134");
//ObserveEvents(channel).Wait();
channel.ShutdownAsync().Wait();
}
catch (RpcException e)
{
Debug.LogError("Connection Error: " + e);
}
counter = 0;
}
}
private void GetJointPosition(Channel channel, string robotName)
{
// Request Axis Values
var client = new JointPositionService.JointPositionServiceClient(channel);
var request = new GetJointPositionRequest { RobotName = robotName };
var response = client.GetJointPositionAsync(request);
// Fill Dictionary
foreach (var i in response.ResponseAsync.Result.Positions)
{
double value = toDegree((double)i.Value);
AxisValues[i.Index] = (float)Math.Round(value, 2);
}
try
{
AxesValuesTexts[0].text = AxisValues[1].ToString();
AxesValuesTexts[1].text = AxisValues[2].ToString();
AxesValuesTexts[2].text = AxisValues[3].ToString();
AxesValuesTexts[3].text = AxisValues[4].ToString();
AxesValuesTexts[4].text = AxisValues[5].ToString();
AxesValuesTexts[5].text = AxisValues[6].ToString();
}
catch (Exception e)
{
Debug.Log("Dictionary problem.");
}
}
private double toDegree(double rad)
{
return (float)(180 / Math.PI) * rad;
}
}
}
我的问题是,首先,如果此方法完全异步,为什么它仍阻止应用程序在Unity中运行,我又如何重新设计它以实现某些目标例如 Udp或Tcp样式?
答案 0 :(得分:0)
感谢@ Ilian,@ zambari,@ Jan Tattermusch,当然还要感谢我的同事Rainer,他是Grpc Api连接界面的创建者。我对我的结构进行了某种程度的更改,现在在Sender和Receiver计算机上都非常有效。下面,我将解释我所做的更改。
我有两个类,两个类都附加到Unity层次结构中的一个gameObject:GrpcSetup.cs和AxeValuesConnectionInterface.cs。我对脚本发表了评论,希望对您有所帮助。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using FlowerShop.Grpc.Service.Joint;
using FlowerShop.Grpc.Service.Joint.Event;
using FlowerShop.Grpc.Service.System;
using UnityEngine;
namespace Scripts
{
public class GrpcSetup : MonoBehaviour
{
private int _loopController = 1;
private Channel _grpcChannel;
private EventService.EventServiceClient _eventService;
private RegisterOnEvent _request;
private IAsyncStreamReader<Any> _responseStream;
private Any _receivedEvent;
private JointPositionChangedEvent _positionChangedEvent;
// this method will be called only one time in start method in another class
// .. I am initializing a channel for Grpc and some required properties for my ObserveEvents method
public void GrpcChannelInitialization()
{
_grpcChannel = new Channel("192.168.100.16", 50001, ChannelCredentials.Insecure);
_eventService = new EventService.EventServiceClient(_grpcChannel);
_request = new RegisterOnEvent();
}
// this method will be called in Update in another class
public async void GrpcUpdateMethod()
{
try
{
// to get the initial axesVales only one time
if (_loopController == 1)
{
await GetJointPositionOnDemand(_grpcChannel, "Flower_li35443");
_loopController = 0;
}
// receiving Events only when they are available
await ObserveEvents();
}
catch (RpcException e)
{
Debug.LogError("Connection Error: " + e);
}
}
// this method will be called every frame, the good thing about it is that, I will only receive events,
// .. when there are some available.
private async Task ObserveEvents()
{
using (var call = _eventService.OnEvent(_request))
{
_responseStream = call.ResponseStream;
if (await _responseStream.MoveNext())
{
Debug.Log("New Event is available.");
_receivedEvent = call.ResponseStream.Current;
if (_receivedEvent.TypeUrl.EndsWith(JointPositionChangedEvent.Descriptor.FullName))
{
_positionChangedEvent = _receivedEvent.Unpack<JointPositionChangedEvent>();
_positionChangedEvent.Positions.ToList().ForEach(i =>
Instance.Ref.AxesValues[i.Index - 1] = (float) Math.Round(i.Value * Mathf.Rad2Deg, 2));
}
}
}
}
// if I want to get Joint values whenever I like, regardless of ObserveEvents method architecture
// .. at this moment, I am calling it, one time in Update method
private async Task GetJointPositionOnDemand(Channel channel, string flowerName)
{
var client = new JointPositionService.JointPositionServiceClient(channel);
var requestLocal = new GetJointPositionRequest {FlowerName= flowerName};
var response = await client.GetJointPositionAsync(requestLocal);
foreach (var i in response.Positions)
{
var value = i.Value * Mathf.Rad2Deg;
Instance.Ref.AxesValues[i.Index - 1] = (float) Math.Round(value, 2);
}
}
// this will be called in Unity reserved method: OnApplicationQuit
// .. so we are trying to get rid of our opened channel
public async Task ChannelShutDown()
{
await _grpcChannel.ShutdownAsync();
}
}
}
和我的AxeValuesConnectionInterface.cs像这样:
using System.Threading.Tasks;
using UnityEngine;
namespace Scripts
{
[RequireComponent(typeof(GrpcSetup))]
public class AxeValuesConnectionInterface : MonoBehaviour
{
private GrpcSetup _grpcSetup;
private float _timeElapsed;
private int _loopController = 2;
private int _loopController1 = 1;
private int _loopController2 = 1;
private int _counterLoop;
private void Start()
{
_grpcSetup = GetComponent<GrpcSetup>();
}
private void Update()
{
GrpcInitialization();
GrpcUpdateMethods();
}
private void OnApplicationQuit()
{
Task.Run(_grpcSetup.ChannelShutDown);
}
private void GrpcInitialization()
{
if (_loopController2 != 1) return;
if (Instance.Ref.ConnectionInterface != Instance.Ci.Grpc) return;
_grpcSetup.GrpcChannelInitialization();
_loopController2 = 0;
}
private void GrpcUpdateMethods()
{
if (Instance.Ref.ConnectionInterface != Instance.Ci.Grpc || !Instance.Ref.FlowerIsPresent) return;
Task.Run(() => _grpcSetup.GrpcUpdateMethod());
}
}
}