Tasks in C++

Hello. My team has been needing to perform more than one function for competition, however, we have absolutely no idea how to multitask in Wind River. We’ve created a function that calculates the angle needed for our shooter to throw Frisbees directly into the goal.

We’ve experimented with using functions before, but when we use them user control is unresponsive until the function ends. To solve this in the past in RobotC for VEX, we’ve used multitasking to run functions while performing other tasks. My programming team and I have been attempting to use multitasking for FRC this year, yet we’re clueless on how to proceed.

Here’s our current version of the code: http://pastebin.com/RWhNkMXY
The segment of code in question is the function called “shoot.” We want to be able to run that function as a separate task.

Any help and suggestions would be much appreciated :slight_smile:

Here’s our team’s task-creation class. Basically, you just implement your own Run() when inheriting from it. Credit to 254 for the genesis of this code.

https://github.com/bobwolff68/FRCTeam1967/blob/master/code/classes/jankyTask.cpp

And here’s a project which uses the task - the team created this in order to better understand how multi-tasking works. Take a look and ask questions if you have any.

https://github.com/bobwolff68/FRCTeam1967/tree/master/code/JankyTaskTestjig

Bob

Am I missing something?

If your not doing this:

Than how are you calling the jankyTask Constuctor without arguments in your example code :confused: ?

It’s been 2 weeks since you posted this and you haven’t come back. No news is good news? If not, and you haven’t figured out threads, you could use a state machine instead.

The arguments are defaulted.

Paul & other interested programmers –

As was noted - default parameters are allowed in C++. If you look at the header file (.h), you’ll see:


class JankyTask {
 public:
  /**
   * @brief Constructor which takes an optional taskname. In the absence of a task name,
   *        a task name will be created based upon the current system-time.
   */
  JankyTask(const char* taskName = NULL, UINT32 priority = Task::kDefaultPriority);
  virtual ~JankyTask();

The constructor calls for a taskName and priority. Each of these parameters has a default value ( = NULL and = Task::kDefaultPriority) respectively. If you use the constructor without passing parameters, these defaults are used. This is a C++'ism…

As for “how it works” – the concept is simple, the semantics can be a bit tricky, but the usage is yet again simple…

If you really are just out to “use it”, then take the example and run with it by implementing your own class based on JankyTask and implement Run().

If you’d like to understand it a bit further, you’ll need to understand the concept of “static functions”. Most classes are defined such that instantiating the class will instantiate one of each of the functions and variables in the class definition. This is “normal” or “simple”. In the case of JankyTask, you’ll see that there is a static function called JankyPrivateStarterTask(). When a function is static, there is only one of them for the entire world. This becomes important here because of the way the WPILib ‘Task’ class works. It expects that you will hand it a function which it will “launch” as the starting function for the new task. No problem yet, right? It is of type “(FUNCPTR)”. Fine - pass it a function. The problem is that in C++, each class and its functions get “name mangled” when the compiler runs and each member of a class is “private to the class” unless something special is done to it… when it is defined as “static”, then it is much more like a C Function and much less like a C++ member of a class. This makes using it in Task() much easier.

When a new task is created, it needs a function to call (FUNCPTR), and when it is told to Task->Start(), it is passed a “data pointer” on its startup. This data pointer, in this case, is a pointer to the JankyTask instance. This is SUPER critical. This allows the JankyPrivateStarterTask to actually make a call to YOUR Run() function since the data pointer is for this particular instance. You’ll see that task->Run() is called any time that the task is enabled within its while loop and it even does a nice 2 ms wait for you as well.

From the outside, the nice thing is that you can instantiate your JankyTask-based class and then operate it from the outside with variables telling it to Pause(), Terminate(), or to get a value, state, set a solenoid, etc.

Note: I’m glossing over a few important multi-tasking principles of how variables are used and when they need to be protected from multiple uses in multiple tasks. These are usually Mutexes or Semaphores or CriticaSections. But in the case of these simpler examples, keeping things rather un-intertwined keeps one from having to cope with these otherwise more complex ideas.

Further questions - lemme know.

:slight_smile:

Good luck at regionals this weekend! We’ll be at Madera - Central Valley Regional in California. Stop by and say hi to The Janksters Team 1967.

Bob Wolff - Mentor.

Thanks I see that now. I want to change the priority so I will have to call with the different priority. Thanks.

Thanks I see that it is defaulted now. As far as “how” it works I had to dust off a 12 year old college knowledge of a C++ obtained when getting my CS degree but I get it (We use C at work). We will not be needing to do any thing that requires locking using mutexs or semaphores so my data structures won’t need to be thread safe. I appreciate the walk-through. Good luck to you too.

You might want to just switch to using the Command based model which takes care of scheduling commands for you. We had been writing our own scheduled objects with update loops we kicked each pass through the loop. We switched to Command this year and it is so much easier.

So in your case of moving the arm, you’d created a “MoveArm” command which subclasses command. There’s a method called when the Command is initted, one that’s called every 20ms while it is alive, another called to ind out if the command is done, another clean up when it is done. Quite tidy.

Having said that, it doesn’t work great if you need better than 20ms resolution on any given feedback measurement (i.e. a fast spinning acutuator).

Yea, our team has not moved to the IterativeRobot base class. That’s required in order to use the Command items, correct?

bob

Command Based programming is based upon the Iterative framework (which you should probably understand before using either) but is its own project type. And these both make it easier for your robot to “multi-task” without you necessarily having to have multiple scheduled tasks. In either you can throw multiple balls in the air (so to speak) on each iteration, and then check back on progress on subsequent iterations.

I had done something similar, but much simpler, than the CBP back in 2009/2010.

Any task can change its own priority with a simple C function …

#include “taskLib.h”

taskPrioritySet(0, iNewPriority);

… where iNewPriority is from 0 to 99, the lower the number the greater the priority

You could just use pthreads.

Or you could usr our pre-made abstractions:

Alex Brinister

Our team is currently trying to get tasking working,
above code show in JankyTask doesn’t work with the 2016 library.

We’re trying to get Tasking working because we would like to try to split driver one and two into two seperate tasks on the RoboRio, does someone know if this is possible?

If so, can someone help us?

You resurrected an old thread from 2013. Anyway, take a look at our code here. It works fine for us using WPILib’s built in task setup (which I think is really just a wrapper for a pthread).

Task is a VXWorks API wrapper around pthreads. Any reason not to use pthreads directly?

Take a look at ::std::thread. It works well, and is much more cross platform. We use it exclusively.

Can you provide a simple example of using ::std::thread that’s compatible with the roborio?

TIA,

Mike

From our code:

-- snip --
    ::frc971::wpilib::GyroSender gyro_sender;
    ::std::thread gyro_thread(::std::ref(gyro_sender));

    //  Main loop here

    // Now cleanup
    gyro_sender.Quit();
    gyro_thread.join();

-- snip --

// Handles reading the gyro over SPI and sending out angles on a queue.
//
// This is designed to be passed into ::std::thread's constructor so it will run
// as a separate thread.
class GyroSender {
 public:
  GyroSender();

  // For ::std::thread to call.
  //
  // Initializes the gyro and then loops until Quit() is called taking readings.
  void operator()();

  void Quit() { run_ = false; }

 private:

  // Readings per second.
  static const int kReadingRate = 200;

  GyroInterface gyro_;

  ::std::atomic<bool> run_{true};
};

There are a bunch of different variants for how to create the ::std::function that ::std::thread takes for a constructor. A simpler version would be to define a void foo(){} and pass that in. You can also use ::std::bind to call arbitrary functions with arbitrary arguments.

Thanks!

Mike