C# 如何确保我的任务知道主线程上设置的标志?
我正在使用Unity游戏引擎进行一些联网,我希望使用内置的语言功能,而不是依赖第三方资产或Unity API。我提出了这个组件,它连接到一个游戏对象。当游戏对象启用时,它应该将UDP数据包发送到指定的目标 那部分有效。我遇到的问题是这个C# 如何确保我的任务知道主线程上设置的标志?,c#,multithreading,unity3d,synchronization,task,C#,Multithreading,Unity3d,Synchronization,Task,我正在使用Unity游戏引擎进行一些联网,我希望使用内置的语言功能,而不是依赖第三方资产或Unity API。我提出了这个组件,它连接到一个游戏对象。当游戏对象启用时,它应该将UDP数据包发送到指定的目标 那部分有效。我遇到的问题是这个发送标志: using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using UnityEngine; n
发送标志:
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;
}
}
}
我遇到的问题是,一旦循环任务正在运行,我就无法让它停止
我的计划是通过将sending
标志设置为false,使其在组件被禁用时停止。我意识到将标志设置为false发生在不同的线程上。我的期望是,是的,sending
标志设置为false和任务访问其值之间的循环可能会再运行几次,但这对我来说是好的
然而,实际发生的情况是,任务似乎从未意识到发送的值已经改变。它保持true
,循环无限期运行,直到我终止程序,我的接收器才停止接收数据包
为什么会这样?我对低级任务/线程有点陌生,但我知道不能保证辅助任务会立即意识到更改。我希望它最终会更新。但是在我的例子中,虽然我可以验证主线程上的发送
标志是否已设置为false,但任务中的循环从未停止
如果非要我猜的话,这似乎与复制值并更改原始值(即引用/按值错误)时所犯的错误相同。该任务是否实际具有发送的副本,而不是对类实例字段的引用?还是我错过了什么
我知道还有其他实现UDP通信的策略,例如使用Async
变体,但是为了学习,我想知道我在这里做错了什么,以及如何在不完全改变策略的情况下修复它。我以前在同一任务中也遇到过同样的问题。使用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。如果有人指出这个问题为什么也是反价值的,那么如果你打算否决它,那么不提供目标IPEndPoint的Send将非常好,因为我显然对此一无所知。尝试将sending
变量声明为。像这样:volatile bool发送代码>@TheodorZoulias根据以下答案:,添加volatile关键字并不能保证字段是线程安全的。@ArutyunEnfendzhyan这种情况与线程安全关系不大,与可见性关系更大。一个线程没有看到另一个线程对变量所做的更改。可见度的话题是非常模糊和复杂的,细节令人毛骨悚然,有时甚至令人恐惧。这里有两个相关的问题:,@TheodorZoulias这正是我所说的线程安全。以下是“线程安全”的定义-线程安全代码仅以确保所有线程正常运行并满足其设计规范的方式操作共享数据结构,而不会发生意外交互。感谢您的回答。我确实也遇到了取消令牌模式-但为了启发起见,我想知道是什么阻止了任务内部拾取此布尔标志的更改-对此有什么想法吗?下面是一个很好的答案:。基本上,为了能够从另一个线程检索数据,您必须使用正确的上下文复制它。CancellationToken只是对CancellationTokenSource的引用的存储,它负责所有事情。@Joseph R您对答案满意吗?请将其标记为已回答。我非常感谢这种替代方法,它很有帮助。但请注意,问题是“哪里错了”——在你看到的评论中也有一些有用的ifno,但没有人将其作为答案的一部分提出。