表現が難しい…。
タスクが非同期で複数殺到して処理待ちになるような状況で、現在処理中のタスクと一番最後に来たタスクだけ残して、その他(中間)はキャンセルとする。というものを実装したくなった。
実現したいこと
下記のようなイメージ。
・タスク1がやってくる
1 → ( )
・タスク1が重めの処理を開始、続けてタスク2がやってくる
2 → (1)
・タスク2はタスク1が処理中なので待機、続けてタスク3がやってくる
3 → [2](1)
・この時点で、
3[2](1)
・タスク2は無かったことになり消滅
3[ ](1)
・タスク3がタスク1の次で待機
[3](1)
例えばスライダーコントロールとかで、値が変化するたびに値に応じて何らかの処理をしないといけないけど、その処理が若干重い場合。
全部律儀にこなしてたらガクガクになっちゃうので、1発目と最後のだけ処理して、他は捨てたい、そんな時。
アルゴリズム
セマフォを2つ使う。1つ目は初期値2、2つ目は初期値1にする。そしてセマフォとは別にカウンタXを用意して、3つ目以降のタスクが来たら増やすようにする。
wait1、wait2はそれぞれセマフォ1、セマフォ2のwaitの箇所。
・タスク1がやってくる
カウンタX wait1 wait2 処理
0 2 1
1 → [ ] [ ] ( )
・タスク1が2か所のwaitを通過し、重めの処理を開始。タスク2が来る
カウンタX wait1 wait2 処理
0 1 0
2 → [ ] [ ] (1)
・タスク2はwait1を通過するが、wait2のカウンタは0なので足止め、タスク3が来る
カウンタX wait1 wait2 処理
0 0 0
3 → [ ] [2] (1)
・wait1の直前でタスク3はwait1のカウンタを見て、0なら…
カウンタX wait1 wait2 処理
0 0 0
3[ ] [2] (1)
・カウンタXを1増やし、セマフォ2とセマフォ1のカウンタを1ずつ解放
カウンタX wait1 wait2 処理
1 1 1
3[ ] [2] (1)
・タスク2とタスク3がそれぞれwaitを通過するが…
カウンタX wait1 wait2 処理
1 0 0
[ ]3 [ ]2 (1)
・wait2の出口には罠が仕掛けてあり、もしカウンタXが0じゃなかった場合、
タスク2はカウンタXを1減らして、自らは退散する
タスク3はwait2で足止め。つまりタスク3が来る前の状態に戻る
カウンタX wait1 wait2 処理
0 0 0
[ ] [3]* (1)
・処理を終えたタスク1は、セマフォ2とセマフォ1のカウンタを1ずつ解放して退散
カウンタX wait1 wait2 処理
0 1 1
[ ] [3] ( )*
・タスク3がwait2を通過して処理に入る。これはタスク2が来る前の状態と同じ
カウンタX wait1 wait2 処理
0 1 0
[ ] [ ] (3)
・タスク3も処理を終えるとセマフォのカウンタを解放して退散
これは一番最初の状態に戻ったことになる
カウンタX wait1 wait2 処理
0 2 1
[ ] [ ] ( )*
こんな感じでイケるんじゃないかと!
実装
C#でやってみたけど、まあ他の言語でも出来そう。
//--------------------
private System.Threading.SemaphoreSlim semaphore1 = new System.Threading.SemaphoreSlim(2, 2);
private System.Threading.SemaphoreSlim semaphore2 = new System.Threading.SemaphoreSlim(1, 1);
private int count_x = 0;
//--------------------
private void CutTask() {
Task.Run(() => {
if (semaphore1.CurrentCount == 0) {
System.Threading.Interlocked.Increment(ref count_x);
semaphore2.Release();
semaphore1.Release();
}
semaphore1.Wait();
semaphore2.Wait();
if (count_x > 0) {
System.Threading.Interlocked.Decrement(ref count_x);
return;
}
HeavyProcess();
semaphore2.Release();
semaphore1.Release();
});
}
//--------------------
感想
4つ目以降のタスクが来たら処理順が変わっちゃうんじゃないかとも思ったけど、3つ目が来てから2つ目が抹殺されて詰められるまでは多分一瞬なので、基本的に一度に3つ以降のタスクが長時間溜まるような状況は無いんじゃないかと思う。あまり自信無し。