almost done with first chapter

This commit is contained in:
Robin Müller 2022-09-28 19:18:21 +02:00
parent 8082d4ae7a
commit 11dc19b6e3
No known key found for this signature in database
GPG Key ID: 11D4952C8CCEF814
3 changed files with 208 additions and 33 deletions

View File

@ -70,11 +70,19 @@ from the scheduling logic inside `executeTask`. This is also called seperation o
## 3. Making the executable objects generic
Our approach is useful buts lacks being generic as it relies on `std` library API. C++ as an OOP
language provides abstraction in form of interfaces, which can be used to have different types of
generic executable objects. Interfaces usually do not have a lot of source code on their own. They
describe a design contract a class should have which implements the interface. In general, the FSFW
relies heavily on subclassing and inheritance to provide adaptions point to users.
Threads generally expect a function which is then directly executed.
Sometimes, the execution of threads needs to be deferred. For example, this can be useful
if the execution of tasks should only start after a certain condition.
Also, it might become useful to model any task in form of a class. An instantiation
of that class would then be an executable object. Another point is that even though
we have seperated the scheduling specific part from the application logic, they are still
part of the same class. It would be nice to have two separate classes for this.
C++ as an OOP language provides abstraction in form of interfaces, which can be used to have
different types of generic executable objects. Interfaces usually do not have a lot of source code
on their own. They describe a design contract a class should have which implements the interface.
In general, the FSFW relies heavily on subclassing and inheritance to provide adaptions point to users.
We are going to refactor our `MyExecutableObject` by introducing an interface for any executable
object. We are then going to add a generic class which expects an object fulfilling this design
@ -112,18 +120,50 @@ pure virtual functions.
are now explicitely decoupled by using composition. Composition means that we have
a "has-a" relationship instead of a "is-a" relationship. In general, composition is preferable
to inheritance for flexible software designs. The new `MyPeriodicTask` class should
have a ctor which expects a `MyExecutableObjectIF` by reference. It caches that object
and exposes a `start` method to start the task
have a ctor which expects a `MyExecutableObjectIF` by reference and the task frequency in
milliseconds as an `uint32_t`. It caches both the executbale object and the task frequency
as private member variables. Also add a `std::thread` member variable.
6. Add a public `start` function and leave it empty for now.
7. Add a private static `executeTask` method which expects `MyPeriodicTask` by reference.
Its implementation is similar to the `executeTask` method of `MyExecutableObject`.
Remove the `executeTask` implementation from `MyExecutableObject`.
8. In the start method, use `std::thread` API with `MyPeriodicTask::executeTask` as the
executed function. Pass the task itself by reference similarly to how it was done in task 2.
Cache the created thread into the thread member variable.
We now have two separate classes where one class only contains application logic and the other
one only contains scheduling logic. The `MyPeriodicTask` is also able to schedule arbitrary
types which implement `MyExecutableObjectIF`. Finish this task
by crating 3 different executable objects where each object does or prints our something
different. Then pass all of those three different objects to a `MyPeriodicTask` and start
all three periodic tasks which three different frequencies.
You now should have code which looks something like this:
```cpp
int main() {
MyExecutableObject0 myExecutableObject0;
MyExecutableObject1 myExecutableObject1;
MyExecutableObject2 myExecutableObject2;
MyPeriodicTask task0(myExecutableObject0, 1000);
MyPeriodicTask task1(myExecutableObject1, 2000);
MyPeriodicTask task2(myExecutableObject2, 5000);
auto thread0 = task0.start();
auto thread1 = task1.start();
auto thread2 = task2.start();
thread0.join();
thread1.join();
thread2.join();
return 0;
}
```
Where the three tasks do their tasks with different frequencies.
## 3. Using the framework abstractions
Threads generally expect a function which is then directly executed.
Sometimes, the execution of threads needs to be deferred. For example, this can be useful
if the execution of tasks should only start after a certain condition.
Also, it might become useful to model any task in form of a class. An instantiation
of that class would then be an executable object. This is precisely what the framework
exposes in form of the [`ExecutableObjectIF`](https://documentation.irs.uni-stuttgart.de/fsfw/development/api/task.html).
We now use framework components to perform the tasks shown above. The framework
exposes an abstractions for executable tasks called [`ExecutableObjectIF`](https://documentation.irs.uni-stuttgart.de/fsfw/development/api/task.html).
It also offers a unform API to execute periodic tasks in form of the
[`PeriodicTaskIF`](https://egit.irs.uni-stuttgart.de/fsfw/fsfw/src/branch/master/src/fsfw/tasks/PeriodicTaskIF.h).
@ -136,10 +176,22 @@ are then executed sequentially. This allows a granular design of executable task
For example, important tasks get an own dedicated thread while other low priority objects are
scheduled consecutively in another thread.
The task abstractions have the following advantages:
In summary, task abstractions have the following advantages:
- Task execution can be deferred until an explicit `start` method is called
- Same uniform API across multiple operating systems
The goal of this task is to implement the task specified in 1 using the
abstractions provided in step 1.
### Subtasks
1. Load the required interfaces:
- `#include "fsfw/tasks/ExecutableObjectIF"`
- `#include "fsfw/tasks/PeriodicTaskIF`
- `#include "fsfw/tasks/TaskFactory`
2. For your three custom objects, implement the executable object IF provided by the framework
instead of your custom interface
3. Create two periodic tasks using the `TaskFactory::createPeriodicTask` function
4. Add the first two of your custom exec objects to the first periodic task. Those tasks
will be executed in the same thread consecutively
5. Add the third custom exec object to the second periodic task. The third object
gets an own thread
6. Start both periodic tasks

View File

@ -3,29 +3,75 @@
using namespace std;
class MyExecutableObject {
class MyExecutableObjectIF {
public:
MyExecutableObject(uint32_t delayMs): delayMs(delayMs) {}
virtual ~MyExecutableObjectIF() = default;
virtual void performOperation() = 0;
};
static void executeTask(MyExecutableObject& self) {
while(true) {
self.performOperation();
this_thread::sleep_for(std::chrono::milliseconds(self.delayMs));
}
}
class MyExecutableObject0: public MyExecutableObjectIF {
public:
MyExecutableObject0() = default;
void performOperation() {
cout << "Hello World" << endl;
void performOperation() override {
cout << "Task 0" << endl;
}
private:
uint32_t delayMs;
};
class MyExecutableObject1: public MyExecutableObjectIF {
public:
MyExecutableObject1() = default;
void performOperation() override {
cout << "Task 1" << endl;
}
private:
};
class MyExecutableObject2: public MyExecutableObjectIF {
public:
MyExecutableObject2() = default;
void performOperation() override {
cout << "Task 2" << endl;
}
private:
};
class MyPeriodicTask {
public:
MyPeriodicTask(MyExecutableObjectIF& executable, uint32_t taskFreqMs)
: executable(executable), taskFreqMs(taskFreqMs) {}
std::thread start() {
return std::thread(
MyPeriodicTask::executeTask,
std::reference_wrapper(*this));
}
private:
static void executeTask(MyPeriodicTask& self) {
while(true) {
self.executable.performOperation();
this_thread::sleep_for(std::chrono::milliseconds(self.taskFreqMs));
}
}
MyExecutableObjectIF& executable;
uint32_t taskFreqMs;
};
int main() {
MyExecutableObject myExecutableObject(1000);
std::thread thread(
MyExecutableObject::executeTask,
std::reference_wrapper(myExecutableObject));
thread.join();
MyExecutableObject0 myExecutableObject0;
MyExecutableObject1 myExecutableObject1;
MyExecutableObject2 myExecutableObject2;
MyPeriodicTask task0(myExecutableObject0, 1000);
MyPeriodicTask task1(myExecutableObject1, 2000);
MyPeriodicTask task2(myExecutableObject2, 5000);
auto thread0 = task0.start();
auto thread1 = task1.start();
auto thread2 = task2.start();
thread0.join();
thread1.join();
thread2.join();
return 0;
}

View File

@ -0,0 +1,77 @@
#include <iostream>
#include <thread>
using namespace std;
class MyExecutableObjectIF {
public:
virtual ~MyExecutableObjectIF() = default;
virtual void performOperation() = 0;
};
class MyExecutableObject0: public MyExecutableObjectIF {
public:
MyExecutableObject0() = default;
void performOperation() override {
cout << "Task 0" << endl;
}
private:
};
class MyExecutableObject1: public MyExecutableObjectIF {
public:
MyExecutableObject1() = default;
void performOperation() override {
cout << "Task 1" << endl;
}
private:
};
class MyExecutableObject2: public MyExecutableObjectIF {
public:
MyExecutableObject2() = default;
void performOperation() override {
cout << "Task 2" << endl;
}
private:
};
class MyPeriodicTask {
public:
MyPeriodicTask(MyExecutableObjectIF& executable, uint32_t taskFreqMs)
: executable(executable), taskFreqMs(taskFreqMs) {}
std::thread start() {
return std::thread(
MyPeriodicTask::executeTask,
std::reference_wrapper(*this));
}
private:
static void executeTask(MyPeriodicTask& self) {
while(true) {
self.executable.performOperation();
this_thread::sleep_for(std::chrono::milliseconds(self.taskFreqMs));
}
}
MyExecutableObjectIF& executable;
uint32_t taskFreqMs;
};
int main() {
MyExecutableObject0 myExecutableObject0;
MyExecutableObject1 myExecutableObject1;
MyExecutableObject2 myExecutableObject2;
MyPeriodicTask task0(myExecutableObject0, 1000);
MyPeriodicTask task1(myExecutableObject1, 2000);
MyPeriodicTask task2(myExecutableObject2, 5000);
auto thread0 = task0.start();
auto thread1 = task1.start();
auto thread2 = task2.start();
thread0.join();
thread1.join();
thread2.join();
return 0;
}