[esp32] vTaskDeleteしたらtask handleもNULLにしたい

# 問題

vTaskDeleteはタスクを終了してくれるが、taskHandleまでは初期化してくれない。

例えばあるタスクが走ってて、そのタスクを終了検知したい場合にそのtaskHandleがNULLになってれば終了とみなすフラグに出来ると思ったが、vTaskDelete自体はtaskHandleの値を変更しないので、一度taskHandleが設定されると自前で消しに行かないといけないし、taskの実行状況を確認するような関数はない模様。

https://forums.freertos.org/t/how-do-i-check-on-the-state-of-a-task-how-do-i-know-when-it-has-been-deleted/11996/3

ここでも`自前でフラグ持て`ともある。

でも[最後の最後に][1]

> if a task want to suicide(delete itself), the opration can diliver to the daemon task( timer task) with a timer pend call, and daemon task should be give a very high priority, so it can do the deletion right away,and then asign NULL to handle of the task to be delete after the delete in the daemon task.
then you can check at other place whether a task exist by checking the value of its handle, be NULL or not.

ということなので、やってみた。

# 解決方法

```c++:main.cpp
#include <Arduino.h>

#if CONFIG_FREERTOS_UNICORE
static const BaseType_t appCpu = 0;
#else
static const BaseType_t appCpu = 1;
#endif

#define ONE_KB 1024

static const char TAG[] = "hello_esp32";

TaskHandle_t taskHandle;

void taskA(void *parameter) {
  while (true) {
    ESP_LOGD(TAG, "task A running");
    // esp32のrtosタスク内でもdelayは呼んで良い
    // 内部的にdelayはarduinoのdelayではなく、
    // vTaskDelay(ms / portTICK_PERIOD_MS);
    // を呼んでいるので問題ない
    delay(100);
  }
}
void deleteDaemonTask(void *parameter) {
  TaskHandle_t *taskHandlePointer = (TaskHandle_t *)parameter;
  TaskHandle_t taskHandleAsLocalValue = *(taskHandlePointer);

  ESP_LOGD(TAG, "parameter %p", parameter);
  ESP_LOGD(TAG, "taskHandlePointer %p", taskHandlePointer);
  ESP_LOGD(TAG, "taskHandleAsLocalValue %p", taskHandleAsLocalValue);
  ESP_LOGD(TAG, "&taskHandleAsLocalValue %p", &taskHandleAsLocalValue);

  ESP_LOGD(TAG, "deleting a task now");
  vTaskDelete(taskHandleAsLocalValue);

  ESP_LOGD(TAG, "setting the task handle NULL");
  *taskHandlePointer = NULL;

  ESP_LOGD(TAG, "ending myself");
  vTaskDelete(NULL);
}

void setup() {
  delay(5000);
  ESP_LOGD(TAG, "[initial] taskHandle %p", taskHandle);

  xTaskCreatePinnedToCore(taskA, "taskA", ONE_KB * 2, NULL, 0, &taskHandle,
                          appCpu);
  while (taskHandle == NULL)
    ;
  ESP_LOGD(TAG, "taskHandle %p", taskHandle);

  delay(1000);
  // ログが被って見にくいので優先順位を上げている
  xTaskCreatePinnedToCore(deleteDaemonTask, "deleteDaemonTask", ONE_KB * 2,
                          &taskHandle, 1, NULL, appCpu);

  ESP_LOGD(TAG, "setup done");
}

void loop() {
  ESP_LOGD(TAG, "in loop");
  ESP_LOGD(TAG, "taskHandle %p", taskHandle);

  if (taskHandle == NULL) {
    ESP_LOGD(TAG, "taskHandle is NULL");
  }
  delay(1000);
}
```

```text:ログ
[D][main.cpp:46] setup(): [initial] taskHandle 0x0
[D][main.cpp:52] setup(): taskHandle 0x3ffb89dc
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:18] taskA(): task A running
[D][main.cpp:58] setup(): setup done
[D][main.cpp:62] loop(): in loop
[D][main.cpp:63] loop(): taskHandle 0x3ffb89dc
[D][main.cpp:29] deleteDaemonTask(): parameter 0x3ffbfda8
[D][main.cpp:30] deleteDaemonTask(): taskHandlePointer 0x3ffbfda8
[D][main.cpp:31] deleteDaemonTask(): taskHandleAsLocalValue 0x3ffb89dc
[D][main.cpp:32] deleteDaemonTask(): &taskHandleAsLocalValue 0x3ffb929c
[D][main.cpp:34] deleteDaemonTask(): deleting a task now
[D][main.cpp:37] deleteDaemonTask(): setting the task handle NULL
[D][main.cpp:40] deleteDaemonTask(): ending myself
[D][main.cpp:62] loop(): in loop
[D][main.cpp:63] loop(): taskHandle 0x0
[D][main.cpp:66] loop(): taskHandle is NULL
[D][main.cpp:62] loop(): in loop
[D][main.cpp:63] loop(): taskHandle 0x0
[D][main.cpp:66] loop(): taskHandle is NULL
[D][main.cpp:62] loop(): in loop
[D][main.cpp:63] loop(): taskHandle 0x0
[D][main.cpp:66] loop(): taskHandle is NULL
[D][main.cpp:62] loop(): in loop
[D][main.cpp:63] loop(): taskHandle 0x0
[D][main.cpp:66] loop(): taskHandle is NULL
```

### 補足：

- 最初はtaskHandleが0x0でNULL
- タスクが始まるとタスクへのアドレス値が入る（ポインター）
- loopでも同じアドレス値が最初は入ってる
- タスクへのパラメーターはポインターで渡すので、タスクへのアドレス値のアドレス値が渡ってる
- &taskHandleAsLocalValueとしてみるとparameter 0x3ffbfda8とアドレスが違うのがわかる
- taskHandleが消されているのでloopでもそれが検出できている


## 解説
普段rubyとjavascriptぐらいしか触らないので、生のポインターを意識するのは頭が疲れる。

ポイントは`TaskHandle_t`はそもそもタスクオブジェクトへのポインターであって[`typedef void * TaskHandle_t;`]、タスクオブジェクト自体ではない。タスクオブジェクトへのポインターをパラメーターとして別のタスクに渡すんだけど、それはポインターで渡さないといけない。
タスクへのポインターを格納しているアドレス（ポインター）をタスクへ渡す。

渡されたタスク側ではそのデータが何なのかを確定させるために
`TaskHandle_t *taskHandlePointer = (TaskHandle_t *)parameter;`
として「これはTaskHandle_tのポインターですよ」と教えてやる。

でもこれはただ単にタスクオブジェクトへ繋がるアドレス値（ポインター）を格納している場所（アドレス＝ポインター）なので、その中身が何なのか
`TaskHandle_t taskHandleAsLocalValue = *(taskHandlePointer);`
として取り出してやらないといけない。

その取り出した値はタスクオブジェクトへのポインターなので
`vTaskDelete(taskHandleAsLocalValue);`
としてやれば指定のタスクが消せる。

その後に
`*taskHandlePointer = NULL;`
としてやって、タスクオブジェクトへのポインターを格納している場所の値はNULLにしてやる。

# 詰まった点

最初はどうしてもポインターから復元したら、rubyやjavascriptのオブジェクトみたいに「オブジェクトを参照している変数」と思い込んでしまっていて`taskHandleAsLocalValue = NULL;`とかしてた。

それだとただ単にタスクオブジェクトの格納場所（アドレス＝ポインター）を数値としてローカル変数上にコピーしただけなので、ここをNULLしても実態としてのtaskHandleは値を持ったまま。

> 最初は
`TaskHandle_t taskHandleAsLocalValue = *(taskHandlePointer);`
は
`TaskHandle_t taskHandle = *(taskHandlePointer);`
としていたので、グローバルの変数の参照と捉えていた
意識的にtaskHandleAsLocalValueとして分かりやすくなった

# 図にするとこう

![Untitled-2021-12-07-0841.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16757/a2761963-7215-0f15-b6e8-44e9eafc435d.png)


## 後で思ったが

```c++
// TaskHandle_t taskHandleAsLocalValue = *(taskHandlePointer);
// 参照値として取ってやればそれはそれで問題なかった...
TaskHandle_t &taskHandleAsLocalValue = *(taskHandlePointer);
taskHandleAsLocalValue = NULL;
```

# 参考

https://techtutorialsx.com/2017/05/07/esp32-arduino-passing-a-variable-as-argument-of-a-freertos-task/

タスクへのパラメータの渡し方が分かりやすく書いてある。


# タスク自身がvTaskDeleteするなら上のようなややこしい事は必要なかった

```c++:main.cpp
#include <Arduino.h>
#include <string>

#if CONFIG_FREERTOS_UNICORE
static const BaseType_t appCpu = 0;
#else
static const BaseType_t appCpu = 1;
#endif

#define ONE_KB 1024

static const char TAG[] = "hello_esp32";

TaskHandle_t taskHandle;

void taskA(void *parameter) {
  auto count = 0;
  while (true) {
    ESP_LOGD(TAG, "task A running");
    delay(100);

    if (++count == 10) {
      // --------------------------------
      // 自分が自分で死ぬなら、死ぬ前にNULLにして死ぬ
      // --------------------------------
      taskHandle = NULL;
      vTaskDelete(NULL);
    }
  }
}

void setup() {
  delay(5000);
  ESP_LOGD(TAG, "[initial] taskHandle %p", taskHandle);

  xTaskCreatePinnedToCore(taskA, "taskA", ONE_KB * 2, NULL, 0, &taskHandle,
                          appCpu);
  while (taskHandle == NULL)
    ;
  ESP_LOGD(TAG, "taskHandle %p", taskHandle);

  ESP_LOGD(TAG, "setup done");
}

void loop() {
  ESP_LOGD(TAG, "in loop");
  ESP_LOGD(TAG, "taskHandle %p", taskHandle);

  if (taskHandle == NULL) {
    ESP_LOGD(TAG, "taskHandle is NULL");
  }
  delay(1000);
}
```

```text:ログ
[D][main.cpp:31] setup(): [initial] taskHandle 0x0
[D][main.cpp:37] setup(): taskHandle 0x3ffb89dc
[D][main.cpp:39] setup(): setup done
[D][main.cpp:43] loop(): in loop
[D][main.cpp:44] loop(): taskHandle 0x3ffb89dc
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:19] taskA(): task A running
[D][main.cpp:43] loop(): in loop
[D][main.cpp:44] loop(): taskHandle 0x3ffb89dc
[D][main.cpp:43] loop(): in loop
[D][main.cpp:44] loop(): taskHandle 0x0
[D][main.cpp:47] loop(): taskHandle is NULL
[D][main.cpp:43] loop(): in loop
[D][main.cpp:44] loop(): taskHandle 0x0
[D][main.cpp:47] loop(): taskHandle is NULL
[D][main.cpp:43] loop(): in loop
[D][main.cpp:44] loop(): taskHandle 0x0
[D][main.cpp:47] loop(): taskHandle is NULL
[D][main.cpp:43] loop(): in loop
[D][main.cpp:44] loop(): taskHandle 0x0
[D][main.cpp:47] loop(): taskHandle is NULL
```

これだけで良かった。

```c++
taskHandle = NULL;
vTaskDelete(NULL);
```

[1]: https://forums.freertos.org/t/how-do-i-check-on-the-state-of-a-task-how-do-i-know-when-it-has-been-deleted/11996/8