Use a binary (or counting) semaphore given from the ISR with the FromISR API, following the deferred-interrupt pattern. The ISR does the minimum, acknowledge the hardware and call xSemaphoreGiveFromISR(handle, &higherPriorityTaskWoken), and then requests a context switch on exit via portYIELD_FROM_ISR(higherPriorityTaskWoken) if giving the semaphore unblocked a higher-priority task. A dedicated task sits blocked on xSemaphoreTake(handle, portMAX_DELAY) and wakes to do the actual heavy processing. This is superior to the bare-metal "set a volatile flag and poll it in the main loop" because the waiting task is genuinely blocked (consuming no CPU) until signaled, and it wakes promptly with the scheduler able to preempt lower-priority work. The critical rules: you must use the FromISR variant (the regular API can't be called from interrupt context), you must not call any blocking or non-FromISR RTOS API in the ISR, and you must keep the ISR short, all the work belongs in the task. A counting semaphore is used instead of binary if events can arrive faster than the task drains them and you must not miss any (the count tallies them). You never take a mutex in an ISR for this.
RTOS & Real-Time Concepts · Interview question
How do you safely signal a task from an ISR?
A strong answer
What a weak answer sounds like
You know the answer. Do you know what gets you dinged?
Pro breaks down the answer most candidates actually give to this question — and the specific reason an interviewer marks it down. It’s the difference between sounding correct and sounding senior, on all 472 questions.
From the lesson
Mutexes & Semaphores
Coordinating concurrent tasks: a mutex for mutual exclusion (ownership + priority inheritance) vs a semaphore for signaling and counting, and why you give a semaphore from an ISR, not take a mutex.