C # в Unity: вызов асинхронного метода Promise Style без блокировки основного потока - PullRequest
0 голосов
/ 04 декабря 2018

У меня есть фрагмент кода, который действует как клиент Grpc в Unity.Стиль кода разработан для консольного приложения, которое можно вызывать в методе Main, блокируя его и постоянно получая данные.Теперь я хочу использовать его в Unity и, очевидно, хочу, чтобы мое приложение работало в Unity одновременно.Кроме того, моя конечная цель - иметь что-то, что работает как Udp Client.Вы вызываете его один раз, и все время будете получать данные для вас, не блокируя какую-либо часть хост-приложения.

Самая важная часть этого проекта заключается в том, что, если будет какое-либо событие, я получуобновить, если нет нового события, соответственно, я не получаю никаких данных.И все это происходит в ObserveEvents (канал) .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 ?

1 Ответ

0 голосов
/ 13 декабря 2018

Спасибо @Ilian, @zambari, @Jan Tattermusch и, конечно, моему коллеге Райнеру, создателю интерфейса подключения Grpc Api для нас.Я до некоторой степени изменил свою структуру, которая теперь работает очень эффективно как на компьютере отправителя, так и на компьютере получателя.Ниже я собираюсь объяснить, что я изменил.

У меня есть два класса, оба прикреплены к одному gameObject в иерархии Unity: 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());
        }
    }
}
...