简体   繁体   中英

What's the purpose of the TPL ContinueWith method?

I'm confused about the TPL ContinueWith method. I don't understand why it's needed. Here's an example from MSDN that shows how to use ContinueWith :

static void SimpleContinuationWithState()
{
   int[] nums = { 19, 17, 21, 4, 13, 8, 12, 7, 3, 5 };
   var f0 = new Task<double>(() => nums.Average());
   var f1 = f0.ContinueWith(t => GetStandardDeviation(nums, t.Result));

   f0.Start();
   Console.WriteLine("the standard deviation is {0}", f1.Result);
}

It seems that I can remove the ContinueWith call without changing the results at all:

static void SimpleContinuationWithState()
{
   int[] nums = { 19, 17, 21, 4, 13, 8, 12, 7, 3, 5 };
   var f0 = new Task<double>(() => GetStandardDeviation(nums, nums.Average()));

   f0.Start();
   Console.WriteLine("the standard deviation is {0}", f0.Result);
}

This standard deviation example must be a contrived example, but I can't think of a reason to ever use ContinueWith. ( unless some library call created the Task instead of me ) In every case, can't I pull the ContinueWith call into the original Task? It'll still run asynchronously. There must be something I'm not understanding.

You're assuming that every single Task is just a synchronous method that's being run in a thread pool thread. That's just not the case, and you shouldn't think of tasks that way. A Task is some kind of work that will finish at some point, possibly generating a value as a result. It could be running a method in a thread pool thread, it could be waiting for an event to fire, it could be waiting for the response to a network request, or for your hard drive to finish writing out some data to a file.

Yes, if your Task is specifically some synchronous code being run in a thread pool thread, and you always want some more synchronous code to run immediately after it on that same thread pool thread, and you're the one in control of creating that Task , and nothing else needs a Task representing that intermediate result, then you can just change what method to use to generate the Task as you showed (and if you are in that situation you're better of doing so). That ends up being a reasonably narrow case though.

As I describe on my blog, there is exactly one use case for ContinueWith : dynamic task parallelism .

It should not be used for:'

  • Asynchronous code.
  • Data parallelism.
  • Static task parallelism.

"Dynamic task parallelism" is when have a bunch of CPU-bound work to do that you want to use multiple threads for ("parallelism"), and you divide your work into multiple CPU-bound tasks ("task parallelism"), and you don't know how many tasks you'll need until you are already processing it ("dynamic task parallelism").

In other words, you should almost never use ContinueWith .

Calling .Result is a blocking operation, for synchronous code - in particular when you know the result is already there. Using ContinueWith is scheduling a callback for when the result becomes available - async code (although not via the async / await pattern). Note that if the result is already available, the continuation is invoked directly back onto the calling thread.

Your existing code is bad and can cause fatal deadlocks. Don't do it :) You are getting away with it in this case , but there are many many gotchas in anything that does "sync over async" (aka "calling .Wait() or accessing .Result ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM