Why is my Task.ContinueWith running 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 is executed as I expect: the first block runs on a background thread, then the second block is executed, and finally the third block is executed.

In .NET 4.5, however, the second block never runs, no matter what happens to the first block (success, error, or cancellation). And the third block also does not start, waiting for the second block that does not start.

Application background

This code is in a WPF application. It runs during the initialization of the application, loading some data required to start the application. On the main thread (where I am calling this asynchronous code), I wait for the result to be populated from code block # 3 before continuing. If the remote data call expires, initialization will continue with the data from block # 1 (cached).

Things i have tried

  • Add TaskScheduler.FromCurrentSynchronizationContext () to the StartNew call. This results in the code in the StartNew lambda never executing (and even if it did, I would not want it to run on the main thread).
  • Remove TaskScheduler.FromCurrentSynchronizationContext () from ContinueWith (and keep either cancelationToken or TaskContinuationOptions, but not both, because the overload doesn't support only those two). It works (code is executed), but I am worried about the side effects because I am not sure why it works.
+3


source to share


1 answer


Here we have an issue with the design of the ContinueWith and TaskScheduler Current and Default properties in these two .Net versions

In .Net 4.0, TaskScheduler Current and Default have the same meaning, i.e. ThreadPoolTaskScheduler , which is the ThreadPool thread scheduler, not the one that updates the UI, i.e. SynchronizationContextTaskScheduler which is why your code is running fine .Net 4.0.

Everything changed in .Net 4.5. So when you say TaskScheduler.Current and TaskScheduler.Default, you get two different schedulers (in your case, WPF)

Current is = SynchronizationContextTaskScheduler



Default = ThreadPoolTaskScheduler

Now, back to your problem. When you use the ContinueWith option, it has a hard-coded value to the scheduler as TaskScheduler.Current. Specifically in WPF and Asp.net, SynchronizationContextTaskScheduler means the synchronization context of the UI thread, and once blocked, nothing else will execute on it until the currently executing thread that is running in the context of the UI thread ends.

Suggestions (.Net 4.5): Try to skip TaskScheduler.Default (NON UI Scheduler) in ContiueWith or not use ContinueWith and rather join the tasks in turn.

Hopefully this will give you a clear picture of why the behavior is changing. See this discussion for more details: Why is TaskScheduler.Current the default TaskScheduler?

+2


source







All Articles