I’m having trouble figuring out how to use the vex::thread constructor with a void * argument, without running out of thread IDs - I noticed that after t.get_id() increases past 129, it wraps around to -1, and the callback function doesn’t seem to execute. Is there any way to reclaim finished thread IDs?
Example code
#include "vex.h"
using namespace vex;
int x;
int *x_ptr = &x;
void foo(void * arg) {
int i = (*(int*) arg)++;
printf("foo %d\n", i);
}
thread t;
int main() {
vexcodeInit();
repeat(130) {
// Calling destructor seems to not affect anything
// t.~thread();
t = thread(foo, x_ptr);
printf("t foo thread id %ld\n", t.get_id());
printf("t foo native handle %p\n", t.native_handle());
task::sleep(10);
}
}
Terminal output
t foo thread id 4
t foo native handle 0x3800100
foo 0
t foo thread id 5
t foo native handle 0x3800100
foo 1
t foo thread id 6
t foo native handle 0x3800100
foo 2
t foo thread id 7
t foo native handle 0x3800100
foo 3
t foo thread id 8
t foo native handle 0x3800100
foo 4
t foo thread id 9
t foo native handle 0x3800100
foo 5
t foo thread id 10
t foo native handle 0x3800100
foo 6
t foo thread id 11
t foo native handle 0x3800100
foo 7
t foo thread id 12
t foo native handle 0x3800100
foo 8
t foo thread id 13
t foo native handle 0x3800100
foo 9
t foo thread id 14
t foo native handle 0x3800100
foo 10
t foo thread id 15
t foo native handle 0x3800100
foo 11
t foo thread id 16
t foo native handle 0x3800100
foo 12
t foo thread id 17
t foo native handle 0x3800100
foo 13
t foo thread id 18
t foo native handle 0x3800100
foo 14
t foo thread id 19
t foo native handle 0x3800100
foo 15
t foo thread id 20
t foo native handle 0x3800100
foo 16
t foo thread id 21
t foo native handle 0x3800100
foo 17
t foo thread id 22
t foo native handle 0x3800100
foo 18
t foo thread id 23
t foo native handle 0x3800100
foo 19
t foo thread id 24
t foo native handle 0x3800100
foo 20
t foo thread id 25
t foo native handle 0x3800100
foo 21
t foo thread id 26
t foo native handle 0x3800100
foo 22
t foo thread id 27
t foo native handle 0x3800100
foo 23
t foo thread id 28
t foo native handle 0x3800100
foo 24
t foo thread id 29
t foo native handle 0x3800100
foo 25
t foo thread id 30
t foo native handle 0x3800100
foo 26
t foo thread id 31
t foo native handle 0x3800100
foo 27
t foo thread id 32
t foo native handle 0x3800100
foo 28
t foo thread id 33
t foo native handle 0x3800100
foo 29
t foo thread id 34
t foo native handle 0x3800100
foo 30
t foo thread id 35
t foo native handle 0x3800100
foo 31
t foo thread id 36
t foo native handle 0x3800100
foo 32
t foo thread id 37
t foo native handle 0x3800100
foo 33
t foo thread id 38
t foo native handle 0x3800100
foo 34
t foo thread id 39
t foo native handle 0x3800100
foo 35
t foo thread id 40
t foo native handle 0x3800100
foo 36
t foo thread id 41
t foo native handle 0x3800100
foo 37
t foo thread id 42
t foo native handle 0x3800100
foo 38
t foo thread id 43
t foo native handle 0x3800100
foo 39
t foo thread id 44
t foo native handle 0x3800100
foo 40
t foo thread id 45
t foo native handle 0x3800100
foo 41
t foo thread id 46
t foo native handle 0x3800100
foo 42
t foo thread id 47
t foo native handle 0x3800100
foo 43
t foo thread id 48
t foo native handle 0x3800100
foo 44
t foo thread id 49
t foo native handle 0x3800100
foo 45
t foo thread id 50
t foo native handle 0x3800100
foo 46
t foo thread id 51
t foo native handle 0x3800100
foo 47
t foo thread id 52
t foo native handle 0x3800100
foo 48
t foo thread id 53
t foo native handle 0x3800100
foo 49
t foo thread id 54
t foo native handle 0x3800100
foo 50
t foo thread id 55
t foo native handle 0x3800100
foo 51
t foo thread id 56
t foo native handle 0x3800100
foo 52
t foo thread id 57
t foo native handle 0x3800100
foo 53
t foo thread id 58
t foo native handle 0x3800100
foo 54
t foo thread id 59
t foo native handle 0x3800100
foo 55
t foo thread id 60
t foo native handle 0x3800100
foo 56
t foo thread id 61
t foo native handle 0x3800100
foo 57
t foo thread id 62
t foo native handle 0x3800100
foo 58
t foo thread id 63
t foo native handle 0x3800100
foo 59
t foo thread id 64
t foo native handle 0x3800100
foo 60
t foo thread id 65
t foo native handle 0x3800100
foo 61
t foo thread id 66
t foo native handle 0x3800100
foo 62
t foo thread id 67
t foo native handle 0x3800100
foo 63
t foo thread id 68
t foo native handle 0x3800100
foo 64
t foo thread id 69
t foo native handle 0x3800100
foo 65
t foo thread id 70
t foo native handle 0x3800100
foo 66
t foo thread id 71
t foo native handle 0x3800100
foo 67
t foo thread id 72
t foo native handle 0x3800100
foo 68
t foo thread id 73
t foo native handle 0x3800100
foo 69
t foo thread id 74
t foo native handle 0x3800100
foo 70
t foo thread id 75
t foo native handle 0x3800100
foo 71
t foo thread id 76
t foo native handle 0x3800100
foo 72
t foo thread id 77
t foo native handle 0x3800100
foo 73
t foo thread id 78
t foo native handle 0x3800100
foo 74
t foo thread id 79
t foo native handle 0x3800100
foo 75
t foo thread id 80
t foo native handle 0x3800100
foo 76
t foo thread id 81
t foo native handle 0x3800100
foo 77
t foo thread id 82
t foo native handle 0x3800100
foo 78
t foo thread id 83
t foo native handle 0x3800100
foo 79
t foo thread id 84
t foo native handle 0x3800100
foo 80
t foo thread id 85
t foo native handle 0x3800100
foo 81
t foo thread id 86
t foo native handle 0x3800100
foo 82
t foo thread id 87
t foo native handle 0x3800100
foo 83
t foo thread id 88
t foo native handle 0x3800100
foo 84
t foo thread id 89
t foo native handle 0x3800100
foo 85
t foo thread id 90
t foo native handle 0x3800100
foo 86
t foo thread id 91
t foo native handle 0x3800100
foo 87
t foo thread id 92
t foo native handle 0x3800100
foo 88
t foo thread id 93
t foo native handle 0x3800100
foo 89
t foo thread id 94
t foo native handle 0x3800100
foo 90
t foo thread id 95
t foo native handle 0x3800100
foo 91
t foo thread id 96
t foo native handle 0x3800100
foo 92
t foo thread id 97
t foo native handle 0x3800100
foo 93
t foo thread id 98
t foo native handle 0x3800100
foo 94
t foo thread id 99
t foo native handle 0x3800100
foo 95
t foo thread id 100
t foo native handle 0x3800100
foo 96
t foo thread id 101
t foo native handle 0x3800100
foo 97
t foo thread id 102
t foo native handle 0x3800100
foo 98
t foo thread id 103
t foo native handle 0x3800100
foo 99
t foo thread id 104
t foo native handle 0x3800100
foo 100
t foo thread id 105
t foo native handle 0x3800100
foo 101
t foo thread id 106
t foo native handle 0x3800100
foo 102
t foo thread id 107
t foo native handle 0x3800100
foo 103
t foo thread id 108
t foo native handle 0x3800100
foo 104
t foo thread id 109
t foo native handle 0x3800100
foo 105
t foo thread id 110
t foo native handle 0x3800100
foo 106
t foo thread id 111
t foo native handle 0x3800100
foo 107
t foo thread id 112
t foo native handle 0x3800100
foo 108
t foo thread id 113
t foo native handle 0x3800100
foo 109
t foo thread id 114
t foo native handle 0x3800100
foo 110
t foo thread id 115
t foo native handle 0x3800100
foo 111
t foo thread id 116
t foo native handle 0x3800100
foo 112
t foo thread id 117
t foo native handle 0x3800100
foo 113
t foo thread id 118
t foo native handle 0x3800100
foo 114
t foo thread id 119
t foo native handle 0x3800100
foo 115
t foo thread id 120
t foo native handle 0x3800100
foo 116
t foo thread id 121
t foo native handle 0x3800100
foo 117
t foo thread id 122
t foo native handle 0x3800100
foo 118
t foo thread id 123
t foo native handle 0x3800100
foo 119
t foo thread id 124
t foo native handle 0x3800100
foo 120
t foo thread id 125
t foo native handle 0x3800100
foo 121
t foo thread id 126
t foo native handle 0x3800100
foo 122
t foo thread id 127
t foo native handle 0x3800100
foo 123
t foo thread id 128
t foo native handle 0x3800100
foo 124
t foo thread id 129
t foo native handle 0x3800100
foo 125
t foo thread id -1
t foo native handle 0x3800100
t foo thread id -1
t foo native handle 0x3800100
t foo thread id -1
t foo native handle 0x3800100
t foo thread id -1
t foo native handle 0x3800100
yea, you can’t with the current scheduler (In the future this will be allowed, I had to add some functionality for the upcoming Python VM), and if you are needing to constantly create new threads with arguments, you are probably using them wrong, perhaps you really just need a function.
Ah ok, thanks. I’m trying to make my own button callback system that only runs a function if no other function in its group is running (for example to prevent two conflicting macros from running simultaneously). I’ve gotten it to work if I don’t pass in a void * argument in the constructor, but then I think the threads can link together in weird ways in certain edge cases. Here’s an example where I can stop a function running on a separate thread unless I reassign the first thread to a different function:
Code
#include "vex.h"
using namespace vex;
void func1() {
printf("start func1\n");
int count = 0;
repeat(5) {
printf("func1 %d\n", count++);
// don't hog the cpu :)
wait(1, sec);
}
printf("end func1\n");
}
void func2() {
printf("func2\n");
}
thread a;
thread b;
int main() {
vexcodeInit();
a = thread(func1);
wait(6, sec);
b = thread(func1);
wait(2, sec);
a.interrupt();
printf("a.interrupt()\n");
printf("\n=========\n");
a = thread(func1);
wait(6, sec);
a = thread(func2); // Reasign thread "a"
wait(1, sec);
b = thread(func1);
wait(2, sec);
a.interrupt();
printf("a.interrupt()\n");
}
I know that most of the time this won’t be an issue, but I can think a few situations where I would want to have the same macro start on separate threads triggered by separate buttons (an example would be if you release a certain button on the primary controller, it stops a macro but only if it was started from the primary controller)
Now that V5 python support is out, is the behavior with creating threads using the constructor with a void* argument the different than before? (I have since switched to PROS so I’m just curious)
Hm, I didn’t change anything in the C++ classes, and threads are not automatically reused. Python uses a C API to free the thread for reuse, it’s probably possible to use that at the end of a thread in C++ but I never tried it. To be honest, it’s not something most of our customers really care about so I was reluctant to change too much in the scheduler and potentially break something. I’ll try a few ideas tomorrow and see if it works for your use case.
The vexos scheduler that VEXcode uses has a fixed amount of resource. We limit the number of active tasks (we use the terminology task and thread interchangeably) to 128. When a program starts, some tasks will be created by the C++ classes because a task is also allocated when an event is created. For example, the brain class will create events for screen press and release.
The scheduler was created late in 2015 as we were exploring how best to port ROBOTC to the V5 brain. We used our understanding of how ROBOTC customers used tasks, and the mistakes they made, to make the scheduler work in much the same way. One incorrect pattern was using a task as if it were a function, that is, the same task was created over and over again in a loop with the expectation it would run each time. For this reason, creating a task in the simplest possible way will not cause a new task to be created but will simply restart the previously created task.
Internally to vexos we had the need to be able to break that paradigm and have multiple tasks that used the same callback, this was needed for the event system. These callbacks also could take a single parameter that was a pointer to user a defined data structure. Initially this was not exposed to customers but was added into the API that VEXcode uses for vexos 1.0.10.
So this is why we have two different patterns when creating tasks. Tasks without arguments will only be able to use the callback once, each time the task is created it will cause that code to start over. Tasks with an argument passed to the callback will create multiple copies, when all vexos resources are exhausted then no more tasks or events will be able to be created. Neither of these features should be a limitation, there should never be a need to have that many tasks running, what exactly are you trying to acheive with the code ?
I try to define a class named ActionUnit, which has a motor_group member variable inside. ActionUnit can be instantiated to intake, carry,shoot ,etc. And ActionUnit has a function void act_for_time( int time ,bool waitForComplete ), which can make an ActionUnit instance to spin for some time in a background thread.
below is an example of ActionUnit that is simplified…
#include "iostream"
#include "vex.h"
using namespace vex;
class ActionUnit {
private:
motor_group g;
struct {
int time;
double speed;
} data;
public:
ActionUnit(motor m) : g(m) {}
static void routine(void *p_this) { // static member function
ActionUnit *p_this_obj = (ActionUnit *)p_this;
p_this_obj->g.spinFor(fwd, p_this_obj->data.time, timeUnits::msec,
p_this_obj->data.speed, velocityUnits::pct);
}
void act_for_time(int time, double speed, bool waitForComplete) {
this->data = {time, speed};
if (!waitForComplete)
thread t(routine, this);
else
g.spinFor(fwd, data.time, timeUnits::msec, data.speed,
velocityUnits::pct);
}
};
motor mtr_carry(PORT3, ratio36_1, false);
motor mtr_shoot(PORT4, ratio36_1, false);
int main() {
ActionUnit carry(mtr_carry);
ActionUnit shoot(mtr_shoot);
carry.act_for_time(1000, 80, false);
shoot.act_for_time(2000, 60, false);
while (1) {
std::cout << "running\n";
this_thread::sleep_for(100);
}
}
One solution is to use events rather than threads. An event is a thread that’s blocks waiting for you to broadcast, (technically, it’s a thread blocked on a semaphore). An event is expected to run and then return waiting for another broadcast.
#include "iostream"
#include "vex.h"
using namespace vex;
class ActionUnit {
private:
motor_group g;
struct {
int time;
double speed;
} data;
event e;
static void routine(void *p_this) { // static member function
ActionUnit *p_this_obj = (ActionUnit *)p_this;
p_this_obj->g.spinFor(fwd, p_this_obj->data.time, timeUnits::msec,
p_this_obj->data.speed, velocityUnits::pct);
}
public:
ActionUnit(motor m) : g(m) {
// create an event
e = event( routine, this );
// small delay so we can potentially call act_for_time immeadiately
this_thread::sleep_for(10);
}
void act_for_time(int time, double speed, bool waitForComplete) {
this->data = {time, speed};
if (!waitForComplete) {
e.broadcast();
}
else {
g.spinFor(fwd, data.time, timeUnits::msec, data.speed,
velocityUnits::pct);
}
}
};
motor mtr_carry(PORT3, ratio36_1, false);
motor mtr_shoot(PORT4, ratio36_1, false);
int main() {
ActionUnit carry(mtr_carry);
ActionUnit shoot(mtr_shoot);
carry.act_for_time(1000, 80, false);
shoot.act_for_time(2000, 60, false);
while (1) {
std::cout << "running\n";
this_thread::sleep_for(500);
}
}
here maybe another BUG !
motor background spinfor( waitForCompletion==false ) has no timeout, it will spin until encoder gets to the desired position, then isDone( ) returns true 。
////////////////////////////////////////
#include "iostream"
#include "vex.h"
using namespace vex;
using namespace std;
motor mtr_carry(PORT3, ratio36_1, false);
int main() {
int TIME_OUT = 1000;
// does not work for background spinFor ( waitForCompletion==false)
mtr_carry.setTimeout(TIME_OUT, timeUnits::msec);
mtr_carry.spinFor(5000, rotationUnits::raw, 100, velocityUnits::pct,
false); // waitForCompletion==false,run background
// another thread stop spinFor , OR the motor is stuck, isDone() will nerver be set to true,
// without timeout( TIME_OUT does not work)
thread t([] {
this_thread::sleep_for(500);
mtr_carry.stop();
});
// bool done_something = false;
while (!mtr_carry.isDone()) {
// if (mtr_carry.position(rotationUnits::raw) > 2000) {
// do_something_else();
// done_something = true;
// }
this_thread::sleep_for(5);
cout << "waiting..\n";
} // wait until spinfor Done or timeout
/*BUG appears!!!!
isDone() is false forever, and the "while(!mtr_carry.isDone())" will run forever....
*/
// go on ...
cout << "go on with other action...\n";
}
So, I suggest motor background spinFor ( waitForCompletion==false) ALSO has a timeout setting , and another funtion returns a boolean when the spinFor timeout, like isTimeout( )~
the “isDone()” function returns true only if the motor achieves the requested position. If you stop the motor and don’t allow it to finish, or if the motor is stalled etc. it will not be set. You should implement an external timeout (if not using the waitForCompletion flag) in that situation if it matters.
when not using waitForCompletion, spinFor will return true for success and false if a timeout occurs.
I want to figure out what spinfor() is doing
it seems spin() would stop spinfor( waitForCompletion=false )
and spinfor( waitForCompletion=true ) will give a stop() command to motor when Timeout expires, even though spin() changed the speed of motor before Timeout expires.
thx!
spinFor asks the motor to goto a given position. It then monitors status from the motor waiting for the motor to indicate that it has reached that position. If you were to send spin from a different task to the same motor, it will never reach the commanded position (the motor will cancel the goto command) so spinFor will probably just timeout as the motor status will never be set.
yes, spinFor( waitForCompletion=true ) will probably just timeout if spin() is sent from a different task ,but before that , spinFor seems to give a stop() command to the motor。
in short , I want to spinFor( waitForCompletion=false ) has also a timeout setting。
motor::setTimeout () only works for spinFor( waitForCompletion=true )。
I think you will need to remember the time when you issued the spinFor(waitForCompletion=false) command yourself, and then regularly check current time and when desired time expires, send the stop command yourself.
In general, I would avoid trying to control same motor from different threads. You can control one or more motors from one thread and another set of motors from another. Then each thread deals with timeout conditions for the motors that it is responsible for.
Or you can use only non-blocking motor commands and do everything from a single main thread.
I thought that vexos only sends motors position and velocity targets and does all timeouts in software on the brain. Is it true? Can you query from user code what targets motor has at any given time? @jpearman
I want to achieve this: in autonomous, main thread aks the motor to spin to a given postion at background with a timeout setting, which means that is the motor stalls, it will stop itself when timeout occurs. then main thread does something else。 after some time, main thread may ask the motor to spin to another position or just simply spin, ect 。 @weilin
I guess, you can do that, but I would do the timing myself in the user code of the main thread and issue motor stop or brake commands. You can try to query motor state when possible, but this way you have the full control and don’t have to guess if motor was disconnected, stalled, or timed out.