TaskFactory を使用して for ループで新しいタスクを開始するときは、現在のループ インデックスをパラメーターとして、タスクを開始するラムダ関数に渡します。このインデックスは、リストからアイテムを選択し、アイテムのワーカー関数を呼び出すために使用されます。ワーカー タスクに確実に渡されるインデックスの値に依存できないようです。
以下のコードを実行すると、コンソール出力で、一部のワーカーが開始されず、一致する数の他のワーカーが少なくとも 2 回開始されたことが明らかになります。次のようになります。
Results:
Started: 7, Completed: 7, Already Started: 2
これは、ループ インデックス値のコピーを取り、これをワーカー関数に渡すことで「解決」できます (これは、Manager RunWorkers 関数でコメント化されたビットです)。これにより、次のような期待される結果が得られます。
Results:
Started: 10, Completed: 10, Already Started: 0
この動作を理解して、適切に防御できるようにしたいと思います(私は愚かなことをしたと思います。「修正」は問題を隠すだけで、信頼できません)
ところで - Manager RunOne 関数でガードを削除すると、インデックスが大きすぎるため、ArgumentOutOfRange 例外が発生する可能性があります。
以下に C# コンソール アプリ (Visual Studio 2013) のコードを含めました。
namespace ThreadingTest
{
public class Worker
{
public bool hasStarted = false;
public bool hasCompleted = false;
public bool hasAlreadyStarted = false;
public readonly int index;
private double value;
public Worker(int _index)
{
index = _index;
}
public void workSocksOff()
{
if (hasStarted)
{
hasAlreadyStarted = true;
return;
}
hasStarted = true;
// Do real work
for (int i=0; i<10000000; ++i)
{
value = Math.Sqrt(i);
}
hasCompleted = true;
}
}
}
それからマネージャー
namespace ThreadingTest
{
public class Manager
{
public List<Worker> Workers = new List<Worker>();
private Object taskLock = new Object();
public int TaskCount { get; set; }
public void RunTest()
{
AddWorkers();
RunWorkers();
}
private void RunWorkers()
{
TaskCount = 0;
TaskFactory taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
Task[] taskPool = new Task[Workers.Count];
for (int i=0; i<Workers.Count; ++i)
{
//int why = i;
//taskPool[i] = taskFactory.StartNew(() => this.RunOne(why))
taskPool[i] = taskFactory.StartNew(() => this.RunOne(i))
.ContinueWith( (antecedant) =>
{
lock (taskLock) { TaskCount += 1; }
}
);
}
Task.WaitAll(taskPool);
}
private void RunOne(int index)
{
if (index >= Workers.Count)
return;
Workers[index].workSocksOff();
}
private void AddWorkers()
{
for (var i = 0; i < 10; ++i)
Workers.Add(new Worker(i));
}
}
}
最後にプログラム自体
namespace ThreadingTest
{
class Program
{
static void Main(string[] args)
{
Manager manager = new Manager();
manager.RunTest();
int started = 0, completed = 0, alreadyStarted = 0;
foreach (Worker w in manager.Workers)
{
if (w.hasStarted) started++;
if (w.hasCompleted) completed++;
if (w.hasAlreadyStarted) alreadyStarted++;
}
Console.WriteLine("Results: ");
Console.WriteLine("\tStarted: {0}, Completed: {1}, Already Started: {2}", started, completed, alreadyStarted);
Console.ReadKey();
}
}
}