Does using Tasks (TPL) library make an application multithreaded?

16,119

Solution 1

Tasks can be used to represent operations taking place on multiple threads, but they don't have to. One can write complex TPL applications that only ever execute in a single thread. When you have a task that, for example, represents a network request for some data, that task is not going to create additional threads to accomplish that goal. Such a program is (hopefully) asynchronous, but not necessarily mutlithreaded.

Parallelism is doing more than one thing at the same time. This may or may not be the result of multiple threads.

Let's go with an analogy here.


Here is how Bob cooks dinner:

  1. He fills a pot of water, and boils it.
  2. He then puts pasta in the water.
  3. He drains the pasta when its done.
  4. He prepares the ingredients for his sauce.
  5. He puts all of the ingredients for his sauce in a saucepan.
  6. He cooks his sauce.
  7. He puts his sauce on his pasta.
  8. He eats dinner.

Bob has cooked entirely synchronously with no multithreading, asynchrony, or parallelism when cooking his dinner.


Here is how Jane cooks dinner:

  1. She fills a pot of water and starts boiling it.
  2. She prepares the ingredients for her sauce.
  3. She puts the pasta in the boiling water.
  4. She puts the ingredients in the saucepan.
  5. She drains her pasta.
  6. She puts the sauce on her pasta.
  7. She eats her dinner.

Jane leveraged asynchronous cooking (without any multithreading) to achieve parallelism when cooking her dinner.


Here is how Servy cooks dinner:

  1. He tells Bob to boil a pot of water, put in the pasta when ready, and serve the pasta.
  2. He tells Jane to prepare the ingredients for the sauce, cook it, and then serve it over the pasta when done.
  3. He waits for Bob and Jane to finish.
  4. He eats his dinner.

Servy leveraged multiple threads (workers) who each individually did their work synchronously, but who worked asynchronously with respect to each other to achieve parallelism.

Of course, this becomes all the more interesting if we consider, for example, whether our stove has two burners or just one. If our stove has two burners then our two threads, Bob and Jane, are both able to do their work without getting in each others way, much. They might bump shoulders a bit, or each try to grab something from the same cabinet every now and then, so they'll each be slowed down a bit, but not much. If they each need to share a single stove burner though then they won't actually be able to get much done at all whenever the other person is doing work. In that case, the work won't actually get done any faster than just having one person doing the cooking entirely synchronously, like Bob does when he's on his own. In this case we are cooking with multiple threads, but our cooking isn't parallelized. Not all multithreaded work is actually parallel work. This is what happens when you are running multiple threads on a machine with one CPU. You don't actually get work done any faster than just using one thread, because each thread is just taking turns doing work. (That doesn't mean multithreaded programs are pointless on one cores CPUs, they're not, it's just that the reason for using them isn't to improve speed.)


We can even consider how these cooks would do their work using the Task Parallel Library, to see what uses of the TPL correspond to each of these types of cooks:

So first we have bob, just writing normal non-TPL code and doing everything synchronously:

public class Bob : ICook
{
    public IMeal Cook()
    {
        Pasta pasta = PastaCookingOperations.MakePasta();
        Sauce sauce = PastaCookingOperations.MakeSauce();
        return PastaCookingOperations.Combine(pasta, sauce);
    }
}

Then we have Jane, who starts two different asynchronous operations, then waits for both of them after starting each of them to compute her result.

public class Jane : ICook
{
    public IMeal Cook()
    {
        Task<Pasta> pastaTask = PastaCookingOperations.MakePastaAsync();
        Task<Sauce> sauceTask = PastaCookingOperations.MakeSauceAsync();
        return PastaCookingOperations.Combine(pastaTask.Result, sauceTask.Result);
    }
}

As a reminder here, Jane is using the TPL, and she's doing much of her work in parallel, but she's only using a single thread to do her work.

Then we have Servy, who uses Task.Run to create a task that represents doing work in another thread. He starts two different workers, has them each both synchronously do some work, and then waits for both workers to finish.

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);
    }
}

Solution 2

A Task is a promise for future work to be completed. When using it, you can use it for I/O based work, which does not require you to be using multiple threads for code execution. A good example is using C# 5's feature of async/await with HttpClient which does network based I/O work.

However, you can take advantage of the TPL to do multithreaded work. For example, when using Task.Run or Task.Factory.Startnew to start a new task, behind the scenes work gets queued for you on the ThreadPool, which the TPL abstracts away for us, allowing you to use multiple threads.

A common scenario for using multiple threads is when you have CPU bound work which can be done simultaneously (in parallel). Working with a multithreaded application comes with great responsibility.

So we see that working with the TPL dosen't necasserly mean using multiple threads, but you definitely can leverage it to do multithreading.

Solution 3

Q: But, just you have used TPL means that you have written an multithreaded application?

Smart question, Task != Multi-threaded, Using TaskCompletionSource you can create a Task which may execute in single thread(may be UI thread only) itself.

Task is just an abstraction over an operation which may complete in future. It doesn't mean code is multi-threaded. Usually Task involves multi-threading, not necessarily always.

And keep in mind only with knowledge of TPL you can't say that you know multi-threading. There are many concepts you need to cover.

  • Thread
  • Synchronization primitives
  • Thread safety
  • Asynchronous programming
  • Parallel programming which is a part of TPL.
  • and lot more ...

and of course Task parallel library too.

Note: this is not the full list, these are just from top of my head.


Resources to learn:

For threading alone, I suggest http://www.albahari.com/threading/

For video tutorials I suggest Pluralsight. It is paid but worth the cost.

Last but not least: Yes of course, It is Stackoverflow.

Solution 4

My 5 cents: you don't have to engage threads explicitly with Task.Run or Task.Factory.StartNew to make your TPL application multithreaded. Consider this:

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();

The code after await inside doAsync is executed concurrently on different threads. In a similar way, concurrency can be introduced with async sockets API, HttpClient, Stream.ReadAsync or anything else that uses thread pool (including IOCP pool threads).

Figuratively speaking, every .NET application is allready multithreaded, because the Framework uses ThreadPool extensively. Even a simple console application will show more than one thread for System.Diagnostics.Process.GetCurrentProcess().Threads.Count. Your interviewer should have asked you about whether you have written concurrent (or parallel) code.

Share:
16,119

Related videos on Youtube

now he who must not be named.
Author by

now he who must not be named.

#SOreadytohelp There is this joy in helping others. Thanks SO

Updated on June 12, 2022

Comments

  • now he who must not be named.
    now he who must not be named. almost 2 years

    Recently when being interviewed, I got this question.

    Q: Have you written multithreaded applications?

    A: Yes

    Q: Care to explain more?

    A: I used Tasks (Task Parallel library) to carry out some tasks like waiting for some info from internet while loading UI. This improves my application usability.

    Q: But, just you have used TPL means that you have written an multithreaded application?

    Me: (Not sure what to say1)

    So, whats exactly a multi-threaded application? Is it different from using Tasks?

  • now he who must not be named.
    now he who must not be named. almost 10 years
    just awesome. Great Analogy. Thanks :)
  • Sriram Sakthivel
    Sriram Sakthivel almost 10 years
    +1 Servy's dinner was delicious, Let's look at Eric Lippert's Breakfast too :)
  • Ed Power
    Ed Power almost 10 years
    Well written! The burner analogy is brilliant.
  • noseratio
    noseratio almost 10 years
    @nowhewhomustnotbenamed, check also Stephen Cleary's blog (and his book too): blog.stephencleary.com. Also, the task-parallel-library tag wiki has some free e-books listed.
  • now he who must not be named.
    now he who must not be named. almost 10 years
    @sriram Sakthivel: Thanks a ton Sriram. Very interesting resources. Thanks again :)
  • bhuvin
    bhuvin almost 10 years
    +1 ! @Servy - Amazing answer. Clearly a Benchmark for SO Users.
  • Haohmaru
    Haohmaru over 8 years
    So much writing on this subject yet this answer just made the lights go on.
  • jim crown
    jim crown about 7 years
    @Servy: Great explanation and analogy. Loved it
  • Krtti
    Krtti almost 7 years
    @Servy : Sir you should actually write a book extending this analogy, thanks for such a great explination.
  • yair
    yair about 6 years
    great explanation. Only one thing regarding That doesn't mean multithreaded programs are pointless on one cores CPUs, they're not, it's just that the reason for using them isn't to improve speed - even on one CPU there could be significant speed gains when the program involves I/O operations (and isn't that essentially the "Jane" example of asynchronous single-threaded application?)
  • Servy
    Servy about 6 years
    @yair That's just strictly inferior to performing the IO asynchronously without using multiple threads though. Why waste a bunch of system resources creating threads just to have them sit there doing nothing productive, when you could just not create additional threads to begin with since you have nothing for them to do. The only real reason to do that is because an API was poorly designed and only exposed synchronous versions of their long running IO operations instead of also providing asynchronous versions, and even then you're just hacking around a badly designed API.