Как я могу убедиться, что моя задача знает о флаге, установленном в главном потоке? - PullRequest
1 голос
/ 04 апреля 2020

Я использую игровой движок Unity для создания сетей и хочу использовать встроенные языковые функции, а не полагаться на сторонние ресурсы или Unity API. Я придумал этот компонент, который присоединяется к GameObject. Когда игровой объект включен, он должен отправлять UDP-пакеты в указанное место назначения.

Эта часть работает. Проблема, с которой я сталкиваюсь, связана с этим флагом sending:

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;

namespace UdpTest
{
    public class DummySender : MonoBehaviour
    {
        public string destinationAddress = "127.0.0.1";
        public int port = 55555;

        UdpClient client;
        bool sending;
        IPEndPoint endpoint;

        private void Awake()
        {
            client = new UdpClient();
            try
            {
                endpoint = new IPEndPoint(IPAddress.Parse(destinationAddress), port);
                client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);
                client.Connect(endpoint);
            }
            catch (Exception e)
            {
                Debug.LogError("Couldn't set up dummy sender: " + e.Message, gameObject);
                gameObject.SetActive(false);
            }
        }

        private void OnEnable()
        {
            sending = true;
            new Task(() =>
            {
                try
                {
                    Debug.Log("Ready to send");
                    while (sending)
                    {
                        var bytes = System.Text.Encoding.ASCII.GetBytes(DateTime.Now.Millisecond.ToString() + "\n");
                        client.Send(bytes, bytes.Length);
                    }
                }
                catch (Exception e)
                {
                    Debug.LogError("Dummy send failed: " + e.Message);
                }
            }).Start();
        }

        private void OnDisable()
        {
            sending = false;
        }
    }
}

Проблема, с которой я столкнулся, заключается в том, что я не могу остановить ее после запуска задачи с l oop. .

Мой план состоял в том, чтобы остановить его, когда компонент отключен, установив для флага sending значение false. Я понимаю, что установка флага в false происходит в другом потоке. Я ожидал, что да, l oop может запускаться еще несколько раз между флагом sending, установленным в false, и задачей, получающей доступ к его значению, но это хорошо для моих целей.

Однако в действительности происходит то, что задача, кажется, никогда не осознает, что значение sending изменилось. Он остается true, l oop работает бесконечно, и мой получатель не прекращает получать пакеты, пока я не убью программу.

Почему это? Я немного новичок в низкоуровневых задачах / потоках, но я понимаю, что нет никакой гарантии, что рабочая задача немедленно узнает об изменении. Я ожидал бы, что это обновится в конечном счете. Но в моем случае, хотя я могу убедиться, что флаг sending был установлен в false в главном потоке, l oop в задаче никогда не останавливается.

Если бы мне пришлось угадывать, это кажется как ошибка того же рода, что и при копировании значения и изменении оригинала (то есть ошибка по ссылке / по значению). Есть ли у задачи копия sending, а не ссылка на поле экземпляра класса? Или я что-то упускаю?

Я знаю, что есть другие стратегии для достижения UDP-связи, например, использование вариантов Async, но ради обучения я хотел бы знать, что я делаю неправильно здесь и как это исправить, не меняя тактику полностью.

1 Ответ

1 голос
/ 04 апреля 2020

Раньше у меня была та же проблема с той же задачей. Исправлено с помощью CancellationTokenSource. Вот как:

 var source = new CancellationTokenSource ();
 Task.Factory.StartNew ( obj => YourMethod ( (CancellationToken) obj ), source.Token, source.Token );

public void YourMethod ( CancellationToken token )
{
    while (!token.IsCancellationRequested)
        ;// insert ur code
}

И чтобы отменить ее, сохраните где-нибудь глобально эту переменную источника, затем, когда хотите отменить вызов:

source.Cancel ();

Также у вас была эта строка:

client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);

, что совершенно бесполезно, поскольку UDP по определению является бесконтактным протоколом, поэтому .Connect и .Send не блокируют поток. Все .Connect делает, чтобы установить IPEndPoint по умолчанию при вызове .Send без предоставления целевого IPEndPoint

...