可変数のワーカースレッドを作成し、それらの間でタスクを分散するスレッドがあります。これは、以下に実装を示すTaskQueueオブジェクトをスレッドに渡すことで解決されます。
これらのワーカースレッドは、与えられたTaskQueueオブジェクトを繰り返し処理し、各タスクを実行します。
private class TaskQueue : IEnumerable<Task>
{
public int Count
{
get
{
lock(this.tasks)
{
return this.tasks.Count;
}
}
}
private readonly Queue<Task> tasks = new Queue<Task>();
private readonly AutoResetEvent taskWaitHandle = new AutoResetEvent(false);
private bool isFinishing = false;
private bool isFinished = false;
public void Enqueue(Task task)
{
Log.Trace("Entering Enqueue, lock...");
lock(this.tasks)
{
Log.Trace("Adding task, current count = {0}...", Count);
this.tasks.Enqueue(task);
if (Count == 1)
{
Log.Trace("Count = 1, so setting the wait handle...");
this.taskWaitHandle.Set();
}
}
Log.Trace("Exiting enqueue...");
}
public Task Dequeue()
{
Log.Trace("Entering Dequeue...");
if (Count == 0)
{
if (this.isFinishing)
{
Log.Trace("Finishing (before waiting) - isCompleted set, returning empty task.");
this.isFinished = true;
return new Task();
}
Log.Trace("Count = 0, lets wait for a task...");
this.taskWaitHandle.WaitOne();
Log.Trace("Wait handle let us through, Count = {0}, IsFinishing = {1}, Returned = {2}", Count, this.isFinishing);
if(this.isFinishing)
{
Log.Trace("Finishing - isCompleted set, returning empty task.");
this.isFinished = true;
return new Task();
}
}
Log.Trace("Entering task lock...");
lock(this.tasks)
{
Log.Trace("Entered task lock, about to dequeue next item, Count = {0}", Count);
return this.tasks.Dequeue();
}
}
public void Finish()
{
Log.Trace("Setting TaskQueue state to isFinishing = true and setting wait handle...");
this.isFinishing = true;
if (Count == 0)
{
this.taskWaitHandle.Set();
}
}
public IEnumerator<Task> GetEnumerator()
{
while(true)
{
Task t = Dequeue();
if(this.isFinished)
{
yield break;
}
yield return t;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
ご覧のとおり、AutoResetEventオブジェクトを使用して、ワーカースレッドが途中で終了しないようにしています。つまり、タスクを取得する前です。
一言で言えば:
- メインスレッドは、タスクをそのTaskQueueにEnqeueueすることによってスレッドにタスクを割り当てます
- メインスレッドは、TaskQueueのFinish()メソッドを呼び出すことにより、実行するタスクがなくなったことをスレッドに通知します。
- ワーカースレッドは、TaskQueueのDequeue()メソッドを呼び出して、割り当てられた次のタスクを取得します
問題は、Dequeue()メソッドがInvalidOperationExceptionをスローし、キューが空であることを示していることです。ご覧のとおり、ログを追加しましたが、 Set()メソッドの呼び出しがなかったとしても、 AutoResetEventはDequeue ()をブロックしないことがわかりました。
私が理解しているように、AutoResetEvent.Set()を呼び出すと、待機中のスレッド(以前はAutoResetEvent.WaitOne()を呼び出していた)を続行でき、次にAutoResetEvent.Reset()を自動的に呼び出して、次のウェイターをブロックします。
では、何が間違っている可能性がありますか?私は何か間違ったことをしましたか?どこかにエラーがありますか?私は今この上に3時間座っていますが、何が悪いのか理解できません。私を助けてください!
どうもありがとうございます!