简体   繁体   中英

Why doesn't my Task.ContinueWith execute in .NET 4.5?

Consider the following code

Task<T>.Factory.StartNew(() => 
    {
      // block #1: load some data from local file cache
    }
  )
  .ContinueWith(task => 
    {
      // block #2: handle success or failure of load-from-cache operation and surface to application
    }, 
    cancellationToken,
    TaskContinuationOptions.NotOnCanceled,
    TaskScheduler.FromCurrentSynchronizationContext()
  )
  .ContinueWith(task => 
    {
      // block #3: load data from remote data source
    },
    TaskContinuationOptions.NotOnCanceled
  );

In .NET 4.0, this code executes as I expect: the first block runs in a background thread, then the second block runs, and finally the third block runs.

In .NET 4.5, however, the second block never runs, no matter what happens with the first block (success, fault, or cancellation). And the third block doesn't run either, waiting for the non-starting second block.

Application background

This code is in a WPF application. It's running during the initialization of the application, loading some data necessary for the app to start. On the main thread (where I call this async code from), I am waiting for the result to be populated from code block #3 before continuing. If the remote data call times out, initialization will continue with the data from block #1 (cached).

Things I've tried

  1. Add the TaskScheduler.FromCurrentSynchronizationContext() to the StartNew call. This causes the code in the StartNew lambda never to execute (and even if it did, I wouldn't want it to run on the main thread).
  2. Remove the TaskScheduler.FromCurrentSynchronizationContext() from the ContinueWith (and keep either the cancellationToken or the TaskContinuationOptions but not both because no overload supports only those two parameters). This appears to work (the code executes), but I'm concerned about side effects because I'm not sure why it works.

Here we have problem with design of ContinueWith and TaskScheduler's properties Current and Default in these two versions of .Net

In .Net 4.0 the TaskScheduler's Current and Default both has same value ie ThreadPoolTaskScheduler , which is ThreadPool's context scheduler and it's not the one that updates the UI ie SynchronizationContextTaskScheduler which is why your code is running fine in .Net 4.0.

In .Net 4.5 things have changed. So when you say TaskScheduler.Current and TaskScheduler.Default then you'll get two different schedulers (in your case when WPF)

Current is = SynchronizationContextTaskScheduler

Default is = ThreadPoolTaskScheduler

Now coming back your problem, When you use ContinueWith option it has hard coded value for scheduler as TaskScheduler.Current. Specifically in WPF and Asp.net SynchronizationContextTaskScheduler means it's the UI thread synchronization context and once it's blocked nothing else will execute associated with it until the current executing thread is finished which running with in the UI Thread context.

Suggestions(.Net 4.5): Try passing TaskScheduler.Default(NON UI Scheduler) in ContiueWith or avoid using ContinueWith and rather join the tasks in queuing manner.

I hope this will give your clear picture why the behaviour is changing. For more details see this discussion: Why is TaskScheduler.Current the default TaskScheduler?

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