Timeouts
Everything that has to do with time runs on the core's timer thread: waits with a timeout, sleeping tasks and tasks started after a delay. This sample turns the timer thread on, lets a task wait on an AutoResetEvent_s with and without a signal, sleeps, starts a task by time, takes back a timed start and turns the timers off again. It measures how long each step took.
Turning timers on
// Without the timer thread a wait with a timeout waits forever and SleepFor
// returns false at once.
if (!TC::EnableTimers())
return 1;EnableTimers starts the timer thread; calls are counted, so a library and the application can both enable it.
The objects
// Only the demo task waits on the event, so the single-waiter variant (with
// timeouts) fits.
TC::Synchronization::AutoResetEvent_s event;
bool setterRan = false;
bool cancelledRan = false;
auto setter = Task::Create([&] {
setterRan = true;
event.Set();
});
auto cancelled = Task::Create([&] { cancelledRan = true; });A wait that times out
// 1. Nobody sets the event: the wait ends after the timeout and returns
// false.
auto start = Clock::now();
timedOut = !event.Wait(Duration::Milliseconds(50));
std::printf("Wait without Set: %s after %lld ms\n",
timedOut ? "timed out" : "signaled",
MillisecondsSince(start));Wait with a Duration returns false when the time is
up. Only the single-waiter variants offer a timeout.
A task started by time
// 2. A task started by time sets the event after 100 ms; the wait with
// a 1 s limit
// returns true as soon as it does. Schedule with a delay must be
// called from a task and fails only without EnableTimers.
start = Clock::now();
if (setter.Schedule(TaskPriority::Default, Duration::Milliseconds(100)))
signaled = event.Wait(Duration::Seconds(1));
std::printf("Wait with a timed Set: %s after %lld ms\n",
signaled ? "signaled" : "timed out",
MillisecondsSince(start));Schedule with a delay adds the task to the calling task's scheduler once the delay has passed. From outside a task, AddTimedTask on the scheduler does the same.
Sleeping
// 3. Sleeping suspends only this task; its thread runs other tasks
// meanwhile.
start = Clock::now();
slept = Task::SleepFor(Duration::Milliseconds(20));
std::printf("SleepFor 20 ms: returned after %lld ms\n",
MillisecondsSince(start));SleepFor suspends only the calling task. Nothing cuts a sleep short; to wait for something with a limit, wait on it with a timeout.
Cancelling a timed start
// 4. A start in 10 s that is taken back: the task exits without
// running.
cancelAccepted
= cancelled.Schedule(TaskPriority::Default, Duration::Seconds(10))
&& cancelled.CancelSchedule();
std::printf("Timed start cancelled: %s\n",
cancelAccepted ? "yes" : "no");CancelSchedule takes back a start that has not happened yet. The task does not run, but it still exits: its exit notification is called, so a group it belongs to counts it out.
Joining and shutting down
// A cancelled task still reports its exit, so the group counts it out like
// the others.
TC::Synchronization::TaskGroup_s group;
group.AddTask(&demo);
group.AddTask(&setter);
group.AddTask(&cancelled);
scheduler->AddTask(&demo);
group.Wait();// Timers off while the scheduler still runs: a timed task that has not
// started yet would exit on its scheduler now.
TC::DisableTimers();
scheduler->Stop();Full program
// Timeouts: a wait with a time limit, a sleeping task, a task started later by time
// and a timed start that is cancelled. All of it needs the core's timer thread
// (EnableTimers).
#define NOMINMAX
#include <Windows.h>
#include <chrono>
#include <cstdio>
#include <ThinkMeta.ConcurrentTasks/Core.h>
import ThinkMeta.ConcurrentTasks.Synchronization.AutoResetEvent;
import ThinkMeta.ConcurrentTasks.Synchronization.TaskGroup;
import ThinkMeta.ConcurrentTasks.Tasks.Task;
namespace TC = ThinkMeta::ConcurrentTasks;
using TC::Duration;
using TC::TaskPriority;
using TC::Tasks::Task;
namespace {
using Clock = std::chrono::steady_clock;
long long MillisecondsSince(Clock::time_point start) noexcept
{
return std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now()
- start)
.count();
}
} // namespace
int main()
{
TC::ConvertThreadToTask();
// Without the timer thread a wait with a timeout waits forever and SleepFor
// returns false at once.
if (!TC::EnableTimers())
return 1;
TC::SchedulerInfo info{};
TC::ITaskScheduler* scheduler
= TC::CreateTaskScheduler(TC::SchedulerKind::Default, &info);
if (scheduler == nullptr)
return 1;
scheduler->Start();
bool ok = false;
{
// Only the demo task waits on the event, so the single-waiter variant (with
// timeouts) fits.
TC::Synchronization::AutoResetEvent_s event;
bool setterRan = false;
bool cancelledRan = false;
auto setter = Task::Create([&] {
setterRan = true;
event.Set();
});
auto cancelled = Task::Create([&] { cancelledRan = true; });
bool timedOut = false;
bool signaled = false;
bool slept = false;
bool cancelAccepted = false;
auto demo = Task::Create([&] {
// 1. Nobody sets the event: the wait ends after the timeout and returns
// false.
auto start = Clock::now();
timedOut = !event.Wait(Duration::Milliseconds(50));
std::printf("Wait without Set: %s after %lld ms\n",
timedOut ? "timed out" : "signaled",
MillisecondsSince(start));
// 2. A task started by time sets the event after 100 ms; the wait with
// a 1 s limit
// returns true as soon as it does. Schedule with a delay must be
// called from a task and fails only without EnableTimers.
start = Clock::now();
if (setter.Schedule(TaskPriority::Default, Duration::Milliseconds(100)))
signaled = event.Wait(Duration::Seconds(1));
std::printf("Wait with a timed Set: %s after %lld ms\n",
signaled ? "signaled" : "timed out",
MillisecondsSince(start));
// 3. Sleeping suspends only this task; its thread runs other tasks
// meanwhile.
start = Clock::now();
slept = Task::SleepFor(Duration::Milliseconds(20));
std::printf("SleepFor 20 ms: returned after %lld ms\n",
MillisecondsSince(start));
// 4. A start in 10 s that is taken back: the task exits without
// running.
cancelAccepted
= cancelled.Schedule(TaskPriority::Default, Duration::Seconds(10))
&& cancelled.CancelSchedule();
std::printf("Timed start cancelled: %s\n",
cancelAccepted ? "yes" : "no");
});
// A cancelled task still reports its exit, so the group counts it out like
// the others.
TC::Synchronization::TaskGroup_s group;
group.AddTask(&demo);
group.AddTask(&setter);
group.AddTask(&cancelled);
scheduler->AddTask(&demo);
group.Wait();
ok = timedOut && signaled && setterRan && slept && cancelAccepted
&& !cancelledRan;
std::printf("%s\n", ok ? "ok" : "UNEXPECTED RESULT");
// Timers off while the scheduler still runs: a timed task that has not
// started yet would exit on its scheduler now.
TC::DisableTimers();
scheduler->Stop();
}
scheduler->Destroy();
TC::ConvertTaskToThread();
return ok ? 0 : 1;
}Build and run
A console program. With the core's props file imported and the library referenced (see Set up a project) it needs nothing else. It takes about a fifth of a second; the measured times may be a little longer on a busy machine:
Wait without Set: timed out after 50 ms
Wait with a timed Set: signaled after 100 ms
SleepFor 20 ms: returned after 20 ms
Timed start cancelled: yes
okWhat to notice
Important
Without EnableTimers a wait with a timeout waits forever, SleepFor returns
false at once and a start by time fails. Turn the timers on before the first task relies on
them.
- A duration is a minimum. The timer fires on a grid of about half a millisecond on current Windows versions; a task may start or wake a little later than asked.
- Single waiter for timeouts. Timeouts exist only on the
_svariants (AutoResetEvent_s, Semaphore_s, WaitableTask_s, TaskGroup_s, …). At most one task may wait on such an object at a time. - A cancelled task still exits. After
CancelSchedulethe object must stay alive until its exit notification has run – here the group takes care of that. - Timers off before the scheduler stops. DisableTimers lets timed tasks that have not started exit on their scheduler, which must still be running then.
- Which thread may call what.
Schedulewith a delay finds the scheduler of the calling task, so it must be called from a task;Setmay be called from anywhere.