[英]C# in Unity: Calling Promise Style Async method without Blocking the Main Thread
我有一個代碼片段,其行為類似於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樣式 ?
感謝@ 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());
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.