Подтвердить что ты не робот

Использует ли использование библиотеки Tasks (TPL) многопоточность приложения?

Недавно, когда меня опросили, я задал этот вопрос.

Q: Вы пишете многопоточные приложения?

A: Да

Q: Поучитесь объяснять больше?

A: Я использовал Tasks (Task Parallel library) для выполнения некоторых задач, таких как waiting for some info from internet while loading UI. Это улучшает удобство использования приложения.

Q: Но только вы использовали TPL означает, что вы написали приложение multithreaded?

Я: (Не уверен, что сказать1)

Итак, что такое многопоточное приложение? Отличается ли это от использования Tasks?

4b9b3361

Ответ 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);
    }
}

Ответ 2

A Task - это обещание завершить будущую работу. При его использовании вы можете использовать его для работы I/O based, который не требует, чтобы вы использовали несколько потоков для выполнения кода. Хорошим примером является использование функции С# 5 async/await с HttpClient, которая работает в режиме ввода-вывода на основе сети.

Однако вы можете использовать TPL для выполнения многопоточной работы. Например, при использовании Task.Run или Task.Factory.Startnew, чтобы запустить новую задачу, за кулисами работа будет стоять в очереди для вас на ThreadPool, которую аннулирует тег TPL для нас, позволяя вам использовать несколько потоков.

Общим сценарием для использования нескольких потоков является работа с ЦП, которая может выполняться одновременно (параллельно). Работа с многопоточным приложением несет большую ответственность.

Итак, мы видим, что работа с TPL dosen't неслучайно означает использование нескольких потоков, но вы определенно можете использовать ее для многопоточности.

Ответ 3

Q: Но, вы использовали TPL, значит, что вы написали многопоточное приложение?

Интеллектуальный вопрос, Задача!= Многопоточность, Используя TaskCompletionSource, вы можете создать Task, который может выполняться в одном потоке (может будь то только пользовательский интерфейс).

Task - это просто абстракция над операцией, которая может завершиться в будущем. Это не означает, что код многопоточный. Обычно Task включает многопоточность, не обязательно всегда.

И имейте в виду только знание TPL, вы не можете сказать, что знаете многопоточность. Существует много концепций, которые вам необходимо охватить.

  • Тема
  • Приоритеты синхронизации
  • Безопасность потока
  • Асинхронное программирование
  • Параллельное программирование, которое является частью TPL.
  • и многое другое...

и, конечно же, параллельная библиотека задач.

Примечание: это не полный список, это только с головы.


Ресурсы для изучения:

Только для потоковой передачи я предлагаю http://www.albahari.com/threading/

Для видеоуроков я предлагаю Pluralsight. Он оплачивается, но стоит затрат.

И последнее, но не менее важное: Да, конечно, Stackoverflow.

Ответ 4

Мои 5 центов: вам не нужно напрямую заниматься потоками с помощью Task.Run или Task.Factory.StartNew, чтобы сделать ваше приложение TPL многопоточным. Рассмотрим это:

async static Task TestAsync()
{
    Func<Task> doAsync = async () =>
    {
        await Task.Delay(1).ConfigureAwait(false);
        Console.WriteLine(new { Thread.CurrentThread.ManagedThreadId });
    };

    var tasks = Enumerable.Range(0, 10).Select(i => doAsync());
    await Task.WhenAll(tasks);
}

// ...
TestAsync().Wait();

Код после await внутри doAsync выполняется одновременно на разных потоках. Аналогично, concurrency может быть введен с асинхронными сокетами API, HttpClient, Stream.ReadAsync или чем-либо еще, использующим пул потоков (включая потоки пула IOCP).

Образно говоря, каждое приложение .NET многопоточно, потому что Framework использует ThreadPool широко. Даже простое консольное приложение отображает более одного потока для System.Diagnostics.Process.GetCurrentProcess().Threads.Count. Ваш собеседник должен был спросить вас о том, написали ли вы параллельный (или параллельный) код.