Параллельная библиотека задач и асинхронные рабочие процессы
У меня есть некоторые вещи, написанные на C#, которые выполняют параллельный код, интенсивно использующий библиотеку параллельных задач (цепочки задач и будущих задач).
Сейчас я портирую это на F# и пытаюсь выяснить плюсы и минусы использования рабочих процессов F# Async по сравнению с конструкциями в TPL. Я склоняюсь к TPL, но думаю, что это можно сделать в любом случае.
У кого-нибудь есть советы и мудрость по написанию параллельных программ на F#, которыми можно поделиться?
2 ответа
Название в значительной степени суммирует разницу: асинхронное программирование против параллельного программирования. Но в F# вы можете смешивать и сочетать.
F# Асинхронные рабочие процессы
Асинхронные рабочие процессы F# полезны, когда вы хотите, чтобы код выполнялся асинхронно, то есть запускал задачу и не ждал окончательного результата. Наиболее распространенное использование этого - операции ввода-вывода. Заставить ваш поток сидеть в бездействующем цикле, ожидая, пока ваш жесткий диск завершит запись ненужных ресурсов.
Если вы начали асинхронную операцию записи, вы можете приостановить поток и позже вызвать его из-за аппаратного прерывания.
Библиотека параллельных задач
Параллельная библиотека задач в.NET 4.0 абстрагирует понятие задачи, такой как декодирование MP3 или чтение некоторых результатов из базы данных. В этих ситуациях вы действительно хотите получить результат вычисления и в какой-то момент времени ожидаете результата операции. (Получив доступ к свойству.Result.)
Вы можете легко смешивать и сочетать эти понятия. Например, выполнение всех ваших операций ввода-вывода в объекте TPL Task. Программисту вы абстрагировали необходимость "разобраться" с этим дополнительным потоком, но под прикрытием вы тратите ресурсы впустую.
Точно так же вы можете создать серию асинхронных рабочих процессов F# и запустить их параллельно (Async.Parallel), но тогда вам нужно дождаться окончательного результата (Async.RunSynchronously). Это освобождает вас от необходимости явно запускать все задачи, но на самом деле вы просто выполняете вычисления параллельно.
По своему опыту я обнаружил, что TPL более полезен, потому что обычно я хочу выполнять N операций параллельно. Тем не менее, асинхронные рабочие процессы F# идеальны, когда есть что-то, что происходит "за кулисами", например, типа "Реактивный агент" или "Тип почтового ящика". (Вы отправляете что-то сообщение, оно обрабатывает его и отправляет обратно.)
Надеюсь, это поможет.
В 4.0 я бы сказал:
- Если ваша функция последовательная, используйте асинхронные рабочие процессы. Они просто читают лучше.
- Используйте TPL для всего остального.
Также возможно смешивать и сочетать. Они добавили поддержку для запуска рабочего процесса как задачи и создания задач, которые следуют асинхронному шаблону Begin/End с использованием TaskFactory.FromAsync, TPL-эквивалента Async.FromBeginEnd или Async.BuildPrimitive
,
let func() =
let file = File.OpenRead("foo")
let buffer = Array.zeroCreate 1024
let task1 = Task.Factory.FromAsync(file.BeginRead(buffer, 0, buffer.Length, null, null), file.EndRead)
task1.Start()
let task2 = Async.StartAsTask(file.AsyncRead(1024))
printfn "%d" task2.Result.Length
Стоит также отметить, что как среда выполнения Async Workflow, так и TPL собираются создать дополнительный примитив ядра (событие) и использовать WaitForMultipleObjects для отслеживания завершения ввода-вывода, а не использования портов завершения и обратных вызовов. Это нежелательно в некоторых приложениях.