我几乎可以肯定这个问题以前曾被问过,但可能不会以我要问的方式。
我正在尝试创建一个名为
DAwaiter
的简单方法,该方法等待直到另一个线程或事件使特定的公共 bool 变量为 true。我并不是试图将变量的新副本传递到方法中,而是尝试不断测试特定的已实例化变量的值。DAwaiter
副本来解决这个问题,但我很懒,并且不想在可以帮助的情况下单独更新每个命令。
我尝试过使用
ref bool waiter
,但不知道如何有效地使用它。我也尝试过使用 object waiter
,但不确定为什么它不起作用。
这是我的例子:
public class MyForm : Form
{
ArduinoComms AConnection = new ArduinoComms();
private void homeButton_Click(object sender, EventArgs e)
{
Task.Run(AConnection.Home);
}
}
public class ArduinoComms
{
public SerialPort Port = new SerialPort(/*parameters here*/); //creates and instances an internal serial port.
Port.DataReceived = new SerialDataReceivedEventHandler(Port_ReceivedData);
public bool XDone, YDone, Homed, Ready, Stopped, Locked = false; //initializes a lot of bools
string NewDataContent = "Default newDataContent - should be inaccessible. If you see this, an error has occurred.";
public void Home()
{
Homed = false;
Ready = false;
XDone = false;
YDone = false;
Logger("Beginning home");
//WriteMyCommand(2); sends command to arduino.
if (!DAwaiter(Homed, true, 250)) { return; }
Logger("finished home, beginning backoff");
XDone = false;
YDone = false;
//WriteMyCommand(0, backoff);
//WriteMyCommand(1, backoff);
if (!DAwaiter(XDone && YDone, true, 250)) { return; }
Logger("Finished home");
Ready = true;
}
internal bool DAwaiter(object waiter, bool expectedValue, int delay)
{
bool waiterVal = (bool)waiter;
CancellationToken ct = cts.Token;
Logger($"{Homed}");
Logger($"Beginning DAwaiter");
while (waiterVal != expectedValue)
{
Logger($"awaiting... {waiterVal}");
Thread.Sleep(delay);
if (ct.IsCancellationRequested)
{
Logger($"Cancelled DAwaiter.");
return false;
}
waiterVal = waiter;
}
Logger($"DAwaiter finished true.");
return true;
}
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
SerialPort spL = (SerialPort)sender; //instances an internal object w/ same type as the event's sender.
byte[] buf = new byte[spL.BytesToRead]; //instantiates a buffer of appropriate length.
spL.Read(buf, 0, buf.Length); //reads from the sender, which inherits the data from the sender, which *is* our serial port.
NewDataContent = $"{System.Text.Encoding.ASCII.GetString(buf)}"; //assembles the byte array into a string.
Logger($"Received: {NewDataContent}"); //prints the result for debug.
string[] thingsToParse = NewDataContent.Split('\n'); //splits the string into an array along the newline in case multiple responses are sent in the same message.
foreach (string thing in thingsToParse) //checks each newline instance individually.
{
switch (thing)
{
case string c when c.Contains("Home done"): //checks incoming data for the arduino's report phrase "Home done" when it is homed.
Homed = true;
Logger($"Homed {Homed}");
break;
default: break; //do nothing
}
}
}
public void Logger(string message)
{
//log the message.
}
}
在此示例中,我使用
DAwaiter
等待串行端口另一端的 arduino 响应本地信号。它通过转动公共变量 Homed = true
来注册这个“home”信号。但是,DAwaiter 方法仅传递 Homed
的起始值,这意味着它永远不会停止等待。
有没有办法让这个传递变量,这样它就会不断测试
Homed == true
中的if语句中是否有DAwaiter
?
这是否可以扩展到多个变量,例如我等待的条件
Axis1 && Axis2 == true
?
非常感谢任何建议。
“Ivan Petrov”提到的问题是,您将 bool 值作为对象传递,然后将其转换为 bool 值。当您执行此装箱操作时,您传递的对象将从引用类型转换为值类型。实现中的另一个问题是,即使您设法仅使用对象将值作为引用类型传递,所使用的机制也是完全不安全的。上述事实是由于多个线程对同一个对象进行读写而引起的,这可能会导致竞争条件。竞争条件是操作系统级别的异常,其中 RAM 内存中的内存地址被执行写入操作的两个或多个线程同时访问。这可能会损坏 RAM 内存中的数据。
解决方案是使用 ref 关键字将 bool 值作为参考值传递。 ref 关键字不会更改 bool 对象的类型,而是将指针传递给堆栈中的值。这是线程安全的,因为变量的内存地址是在线程之间传递的。如果您尝试在线程之间传递堆分配的对象,建议使用
lock
语句,将堆分配的地址固定在堆栈上,直到线程完成该对象的过程。
public static void Main(){
string thread_name = String.Empty;
bool value = false;
// Create and start 10 threads
for(int i = 0; i < 10; i++)
new Thread(()=>{ThreadTask(ref thread_name, ref value);}).Start();
// On the original thread 'lock' the string value and read both the bool and string value
while(true){
lock(thread_name){
Console.WriteLine(thread_name);
Console.WriteLine($"{value}\n\n");
Thread.Sleep(500);
}
}
}
public static void ThreadTask(ref string thread_name, ref bool value){
while(true){
lock(thread_name){
thread_name = $"Thread Id: {Thread.CurrentThread.ManagedThreadId}";
value = !value;
}
}
}