Unity中的C#:在不阻塞主线程的情况下调用Promise Style Async方法

时间:2018-12-03 22:02:14

标签: c# multithreading unity3d asynchronous grpc

我有一个代码片段,其功能类似于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样式

1 个答案:

答案 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());
        }
    }
}