Ответ 1
Задачи могут использоваться для представления операций, происходящих на нескольких потоках, но им этого не нужно. Можно написать сложные приложения TPL, которые выполняются только в одном потоке. Когда у вас есть задача, которая, например, представляет сетевой запрос для некоторых данных, эта задача не собирается создавать дополнительные потоки для достижения этой цели. Такая программа (надеюсь) асинхронна, но не обязательно mutlithreaded.
Parallelism делает несколько одновременных действий. Это может быть или не быть результатом нескольких потоков.
Перейдем к аналогии здесь.
Вот как Боб готовит обед:
- Он наполняет горшок водой и кипит его.
- Затем он кладет макароны в воду.
- Он истощает макароны, когда это делается.
- Он готовит ингредиенты для своего соуса.
- Он ставит все ингредиенты для своего соуса в кастрюле.
- Он готовит свой соус.
- Он ставит свой соус на макароны.
- Он ест ужин.
Боб приготовил полностью синхронно без многопоточности, асинхронности или parallelism при приготовлении его обеда.
Вот как Джейн готовит обед:
- Она наполняет горшок водой и начинает кипеть.
- Она готовит ингредиенты для своего соуса.
- Она ставит макароны в кипящую воду.
- Она кладет ингредиенты в кастрюлю.
- Она истощает макароны.
- Она ставит соус на макароны.
- Она ест свой обед.
Джейн использовала асинхронную кулинарию (без многопоточности), чтобы достичь parallelism при приготовлении ее обеда.
Вот как Servy готовит обед:
- Он говорит Бобу вскипятить горшок с водой, положить в макароны, когда он готов, и служить макаронам.
- Он говорит Джейн подготовить ингредиенты для соуса, приготовить его, а затем подавать его по макаронам, когда это делается.
- Он ждет, пока Боб и Джейн закончат.
- Он ест свой обед.
Servy задействовал несколько потоков (работников), каждый из которых индивидуально выполнял свою работу синхронно, но кто работал асинхронно друг с другом для достижения parallelism.
Конечно, это становится тем более интересным, если учесть, например, что у нашей печи две горелки или только одна. Если в нашей печи есть две горелки, то наши две нити, Боб и Джейн, могут делать свою работу, не вдаваясь друг в друга. Они могут немного ударить по плечам, или каждый пытается время от времени что-то захватывать из того же кабинета, поэтому они будут немного замедлиться, но не так много. Если каждый из них должен использовать одну печь, хотя на самом деле они вообще не смогут многое сделать, когда другой человек выполняет работу. В этом случае работа на самом деле не будет выполняться быстрее, чем просто один человек, делающий кулинарию полностью синхронно, как это делает Боб, когда он сам по себе. В этом случае мы готовим несколько потоков, но наша готовка не распараллеливается. Не все многопоточные работы на самом деле параллельны работе. Это то, что происходит, когда вы запускаете несколько потоков на машине с одним процессором. На самом деле вы не выполняете работу быстрее, чем просто используя один поток, потому что каждый поток просто по очереди делает работу. (Это не означает, что многопоточные программы бессмысленны для процессоров с одним сердечником, это не так, просто причина для их использования - не улучшать скорость.)
Мы даже можем рассмотреть, как эти повара будут выполнять свою работу с помощью параллельной библиотеки задач, чтобы узнать, какие применения TPL соответствуют каждому из этих типов поваров:
Итак, сначала у нас есть bob, просто пишущий обычный код без TPL и делая все синхронно:
public class Bob : ICook
{
public IMeal Cook()
{
Pasta pasta = PastaCookingOperations.MakePasta();
Sauce sauce = PastaCookingOperations.MakeSauce();
return PastaCookingOperations.Combine(pasta, sauce);
}
}
Затем у нас есть Джейн, которая запускает две разные асинхронные операции, а затем ждет их обоих после запуска каждого из них, чтобы вычислить ее результат.
public class Jane : ICook
{
public IMeal Cook()
{
Task<Pasta> pastaTask = PastaCookingOperations.MakePastaAsync();
Task<Sauce> sauceTask = PastaCookingOperations.MakeSauceAsync();
return PastaCookingOperations.Combine(pastaTask.Result, sauceTask.Result);
}
}
В качестве напоминания здесь Джейн использует TPL, и она делает большую часть своей работы параллельно, но она использует только один поток для выполнения своей работы.
Затем мы имеем Servy, который использует Task.Run
для создания задачи, которая представляет работу в другом потоке. Он начинает двух разных рабочих, каждый из них синхронно выполняет какую-то работу, а затем ждет, пока оба работника закончат.
public class Servy : ICook
{
public IMeal Cook()
{
var bobsWork = Task.Run(() => PastaCookingOperations.MakePasta());
var janesWork = Task.Run(() => PastaCookingOperations.MakeSauce());
return PastaCookingOperations.Combine(bobsWork.Result, janesWork.Result);
}
}