简体   繁体   English

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

[英]C# in Unity: Calling Promise Style Async method without Blocking the Main Thread

I have a code snippet which is acting like a Grpc Client in Unity. 我有一个代码片段,其行为类似于Unity中的Grpc客户端。 The code style is designed for a console application, which can be called in Main method, blocking it and receiving data all the time. 该代码样式是为控制台应用程序设计的,可以在Main方法中调用它,阻止它并始终接收数据。 Now, I want to use it in Unity and obviously I want my app to run in Unity at the same time. 现在,我想在Unity中使用它,显然我希望我的应用程序同时在Unity中运行。 Also, my end goal is to have something that works like a Udp Client. 另外,我的最终目标是拥有像Udp Client这样的产品。 You call it one time, and all the time will receive data for you, without blocking any part of the host application. 您一次调用它,所有时间都会为您接收数据,而不会阻塞主机应用程序的任何部分。

The most important part of this design is that, if there is any event, I will get update, if there is no new event, accordingly, I am not receiving any data. 该设计最重要的部分是,如果有任何事件,我将获得更新,如果没有新事件,则我将不会接收任何数据。 And it happens all in ObserveEvents(channel).Wait(); 它发生在ObserveEvents(channel).Wait();中。 . The problem is Wait(); 问题是Wait(); . Which is all the time, keep the main thread, working thread, listening to updates. 一直保持主线程,工作线程在监听更新。 In Play mode Unity does not respond anymore! 在播放模式下,Unity不再响应!

I can bypass it and say, I don't need such a design, I can receive events every other second or every some frames. 我可以绕过它说,我不需要这样的设计,我可以每隔一秒钟或每隔几帧接收一次事件。 By doing that, I have my Unity application, but I am losing so many frame rates, regardless of the fact that my data are NOT flowing smoothly to my host application in 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;
            }
        }
    }
}

And I am calling it like: 我这样称呼它:

var grpcChannel = new GrpcChannel();
grpcChannel.GrpcServer("192.168.123.16", 30201);

in Update() method. 在Update()方法中。 Unfortunately, it does not work in Start() method. 不幸的是,它在Start()方法中不起作用。 And yes, apparently, every frame it needs to create a new Channel, otherwise it will not work. 是的,显然,每个框架都需要创建一个新频道,否则它将无法正常工作。

And the current implementation is like this, which is calling it every 7 frames without using that special wait for events design: 当前的实现是这样的,它每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;
        }
    }
}

My Question is that, first, if this method is completely async , why does it still block the application in Unity, also how I can redesign it to achieve something like Udp or Tcp style ? 我的问题是,首先,如果此方法是完全异步的 ,为什么它仍会阻止 Unity中的应用程序 ,还应该如何重新设计它以实现Udp或Tcp样式

Thanks to @Ilian, @zambari, @Jan Tattermusch and of course my colleague Rainer, the creator of the Grpc Api connection interface for us. 感谢@ Ilian,@ zambari,@ Jan Tattermusch,当然还要感谢我的同事Rainer,他是Grpc Api连接界面的创建者。 I changed my structure to some degree that now works very performant both on Sender and Receiver computer. 我对我的结构进行了某种程度的更改,现在在Sender和Receiver计算机上都非常有效。 Below, I am going to explain what I have changed. 下面,我将解释我所做的更改。

I have two classes, both are attached to one gameObject in Unity hierarchy: GrpcSetup.cs and AxeValuesConnectionInterface.cs. 我有两个类,两个类都附加到Unity层次结构中的一个gameObject上:GrpcSetup.cs和AxeValuesConnectionInterface.cs。 I commented on scripts, I hope it helps. 我对脚本发表了评论,希望对您有所帮助。

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

and my AxeValuesConnectionInterface.cs goes like this: 我的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM