Таким образом, вы обеспокоены тем, что нижеприведенная программа будет содержать ошибки и не сообщит правильное конечное значение 1 000 000 для счетчика:
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
const int TASKS_COUNT = 1000;
const int LOOPS_COUNT = 1000;
ThreadPool.SetMinThreads(100, 10); // Ensure that we have more threads than cores
var locker = new object();
var counter = 0;
var tasks = Enumerable.Range(1, TASKS_COUNT).Select(async x =>
{
for (int i = 0; i < LOOPS_COUNT; i++)
{
await Task.Yield();
lock (locker)
{
counter++;
}
}
}).ToArray();
Task.WaitAll(tasks);
Console.WriteLine($"Counter: {counter:#,0}");
}
}
Вывод:
Счетчик: 1 000 000
Причина правильности этой программы заключается в том, что поток ThreadPool
прерывается операционной системой в середине вычисления не -atomi c counter++
, она возобновится с тем же вычислением, когда получит следующий временной интервал от операционной системы. TaskScheduler
не запланирует выполнение другой задачи в том же потоке до завершения предыдущей задачи, выполняющейся в этом потоке.
Стоит отметить, что с точки зрения TaskScheduler
, каждый путь кода между двумя операторами await
составляет отдельный мини Task
. Разделение выполняется с помощью конечного автомата asyn c. В примере программы свыше 1000 задач создаются явно, но фактическое количество задач, созданных конечным автоматом asyn c, составляет в общей сложности 1 000 000. Все эти задачи были запланированы с помощью метода TaskScheduler.Current.QueueTask
.