7) Технология LINQ To Objects / Многопоточное программирование с использованием задач

Технология LINQ To Objects
Платформа .NET версии 3.5 представила новую технологию работы с коллекциями – Language Integrated Query (LINQ). По типу обрабатываемой информации LINQ делится на LINQ to Objects – библиотеки для обработки коллекций объектов в памяти, LINQ to SQL – библиотеки для работы с базами данных, LINQ to XML предназначена для обработки XML-информации. LINQ to Objects – это набор классов, содержащих типичные методы обработки коллекций: поиск данных, сортировка, фильтрация. Ядром LINQ to Objects является статический класс Enumerable. Этот класс содержит набор методов расширения интерфейса IEnumerable<T>, которые в дальнейшем будут называться операторами LINQ.
Деление операторов LINQ на группы в зависимости от выполняемых действий:
1. Оператор условия Where (фильтрация коллекции)
var list = new List<int> {1, 3, -1, -4, 7}; var r1 = list.Where(x => x < 0);
2. Операторы проекций (выборка информации).
var r1 = gr.Select(student => student.Name);
3. Операторы упорядочивания (сортировка). //OrderBy по возрастанию
var r2 = gr.OrderBy(student => student.Age);// OrderByDescending наоборот
4. Оператор группировки GroupBy (разбивает коллекцию на группы элементов с одинаковым значением ключа). 
var r1 = gr.GroupBy(student => student.Age);
5. Операторы соединения (соединить две коллекции, элементы которых имеют общие атрибуты) 
Join , ("abc" + "def" == String.Concat("abc", "def")?)
6. Операторы работы с множествами. Оператор Distinct() удаляет из коллекции повторяющиеся элементы. Операторы Union(), Intersect() и Except() представляют объединение, пересечение и разность двух множеств.
7. Операторы агрегирования(результатом которых является скалярное значение). Count, Min, Max, Sum, Оператор Aggregate() позволяет выполнить для коллекции собственный алгоритм агрегирования.
8. Операторы генерирования (позволяет создать набор данных). Range(). Он просто выдаёт указанное количество подряд идущих целых чисел, начиная с заданного значения. 
var primes = Enumerable.Range(2, 999).Where(x => !Enumerable.Range(2, (int) Math.Sqrt(x)) .Any(y => x != y && x%y == 0));
Repeat(). Он создаёт коллекцию, в которой указанный элемент повторяется требуемое число раз.
9. Операторы кванторов и сравнения ( операторы в математической логике.)
10. Операторы разбиения (выделяют некую часть исходной коллекции (например, первые десять элементов)). Комбинация операторов Take() и Skip().
11. Операторы элемента(выделения из единственного элемента). First,Last
12. Операторы преобразования.( преобразование универсальных коллекций, реализующих IEnumerable<T>, в конкретные типы) ToArray() ,ToList()

Многопоточное программирование с использованием задач
Поток, рассматриваемый как объект класса Thread, – это низкоуровневый инструмент для организации параллельной работы, и, будучи таковым, он обладает рядом ограничений. В частности, у потоков отсутствует механизм продолжений, когда после завершения метода, работающего в потоке, в этом же потоке автоматически запускается другой заданный метод. Затруднено получение значения функции, выполняющейся в отдельном потоке. Наконец, необдуманное создание множества потоков ведёт к повышенному расходу памяти и замедлению работы приложения. 
Задача (task) – это сущность, в целом подобная потоку. Однако, являясь абстракцией более высокого уровня, она призвана устранить указанные выше ограничения потоков. Для описания задач используются объекты классов Task и Task<TResult>, размещённых в пространстве имён System.Threading.Tasks. Класс TaskFactory содержит набор методов, соответствующих некоторым сценариям использования задач: StartNew(), FromAsync(), ContinueWhenAll(), ContinueWhenAny(). Экземпляр TaskFactory доступен через статическое свойство Task.Factory. Вызов Task.Run() – это сокращение для Task.Factory.StartNew().
Action action = () => Console.WriteLine("Hello"); // аналог предыдущего примера
var task = Task.Factory.StartNew(action);
Для создания задачи можно использовать один из перегруженных конструкторов класса Task. При этом указывается аргумент типа Action – метод, выполняемый в задаче. Если необходимо выполнить метод с параметром, используется Action<object> и дополнительный аргумент типа object.
Action action = () => Console.WriteLine("Hello");
Action<object> method = x => {
Thread.Sleep(1000);
Console.WriteLine(x.ToString());
};
var task1 = new Task(action);
var task2 = new Task(method, 25);
var task1 = new Task(action, TaskCreationOptions.LongRunning);
Метод Start() запускает задачу, вернее, помещает её в очередь запуска планировщика задач. По умолчанию применяется планировщик на основе пула потоков, но существует возможность использовать пользовательский планировщик. Метод RunSynchronously() выполняет задачу синхронно (в терминах используемого планировщика задач).
task1.Start(); // асинхронный запуск
task2.RunSynchronously(); // синхронный запуск
Методы класса Task Wait(), WaitAll() и WaitAny() останавливают основнойпоток до завершения задачи (или задач). Перегруженные версии методов позволяют указать период ожидания завершения и токен отмены.
task1.Wait(1000); Task.WaitAll(task1, task2);
Класс Task<TResult> наследуется от Task и описывает задачу, возвращаю-щую значение типа TResult. Дополнительно к элементам базового класса, Task<TResult> объявляет свойство Result для хранения вычисленного значения.Конструкторы класса Task<TResult> принимают аргументы типа Func<TResult> и Func<object, TResult> (опционально – аргументы типа CancellationToken и TaskCreationOptions).
Func<int> func = () => {
Thread.Sleep(2000);
return 100;
};
var task = new Task<int>(func);
Console.WriteLine(task.Status); // Created
task.Start();
Console.WriteLine(task.Status); // WaitingToRun
task.Wait();
Console.WriteLine(task.Result); // 100
В отличие от потоков, задачи легко распространяют исключения. Если код внутри задачи генерирует необработанное исключение (задача отказывает), это исключение автоматически повторно сгенерируется при вызове метода Wait()
Продолжение сообщает задаче, что после её завершения она должна продолжить делать что-то другое. Первый способ организации продолжения заключается в использовании экземплярного метода задачи ContinueWith().
var task1 = Task.Run(() => Console.Write("Task.."));
var task2 = task1.ContinueWith(t => Console.Write("continuation"));
После того как задача task1 (предшественник) завершается, отказывает или отменяется, задача task2 (продолжение) запускается. Аргумент t, переданный лямбда-выражению продолжения, – это ссылка на предшествующую задачу. Выполнение продолжения можно запланировать на основе завершения множества предшествующих задач при помощи статических методов
Task.WhenAll() и Task.WhenAny().
var task1 = Task.Run(() => Console.Write("X"));
var task2 = Task.Run(() => Console.Write("Y"));
var continuation = Task.WhenAll(task1, task2)
.ContinueWith(t => Console.Write("Done"));