| 97 | // ---------------------------------------------------------------------------------------- |
| 98 | |
| 99 | int main() try |
| 100 | { |
| 101 | // tell the logger to print out everything |
| 102 | dlog.set_level(LALL); |
| 103 | |
| 104 | |
| 105 | dlog << LINFO << "schedule a few tasks"; |
| 106 | |
| 107 | test taskobj; |
| 108 | // Schedule the thread pool to call taskobj.mytask(). Note that all forms of |
| 109 | // add_task() pass in the task object by reference. This means you must make sure, |
| 110 | // in this case, that taskobj isn't destructed until after the task has finished |
| 111 | // executing. |
| 112 | tp.add_task(taskobj, &test::mytask); |
| 113 | |
| 114 | // This behavior of add_task() enables it to guarantee that no memory allocations |
| 115 | // occur after the thread_pool has been constructed, so long as the user doesn't |
| 116 | // call any of the add_task_by_value() routines. The future object also doesn't |
| 117 | // perform any memory allocations or contain any system resources such as mutex |
| 118 | // objects. If you don't care about memory allocations then you will likely find |
| 119 | // the add_task_by_value() interface more convenient to use, which is shown below. |
| 120 | |
| 121 | |
| 122 | |
| 123 | // If we call add_task_by_value() we pass task objects to a thread pool by value. |
| 124 | // So in this case we don't have to worry about keeping our own instance of the |
| 125 | // task. Here we create a lambda function and pass it right in and everything |
| 126 | // works like it should. |
| 127 | dlib::future<int> num = 3; |
| 128 | tp.add_task_by_value([](int& val){val += 7;}, num); // adds 7 to num |
| 129 | int result = num.get(); |
| 130 | dlog << LINFO << "result = " << result; // prints result = 10 |
| 131 | |
| 132 | |
| 133 | // dlib also contains dlib::async(), which is essentially identical to std::async() |
| 134 | // except that it launches tasks to a dlib::thread_pool (using add_task_by_value) |
| 135 | // rather than starting an unbounded number of threads. As an example, here we |
| 136 | // make 10 different tasks, each assigns a different value into the elements of the |
| 137 | // vector vect. |
| 138 | std::vector<std::future<unsigned long>> vect(10); |
| 139 | for (unsigned long i = 0; i < vect.size(); ++i) |
| 140 | vect[i] = dlib::async(tp, [i]() { return i*i; }); |
| 141 | // Print the results |
| 142 | for (unsigned long i = 0; i < vect.size(); ++i) |
| 143 | dlog << LINFO << "vect["<<i<<"]: " << vect[i].get(); |
| 144 | |
| 145 | |
| 146 | // Finally, it's usually a good idea to wait for all your tasks to complete. |
| 147 | // Moreover, if any of your tasks threw an exception then waiting for the tasks |
| 148 | // will rethrow the exception in the calling context, allowing you to handle it in |
| 149 | // your local thread. Also, if you don't wait for the tasks and there is an |
| 150 | // exception and you allow the thread pool to be destructed your program will be |
| 151 | // terminated. So don't ignore exceptions :) |
| 152 | tp.wait_for_all_tasks(); |
| 153 | |
| 154 | |
| 155 | /* A possible run of this program might produce the following output (the first |
| 156 | column is the time the log message occurred and the value in [] is the thread |
nothing calls this directly
no test coverage detected