20

I have an WebService that creates a task and a continuation task.

In the first task we set Thread.CurrentPrincipal

Hence, When the ContinuationTask starts it no longer has the Thread.CurrentPrincipal.

I'd like to specify in the ContinuationTask that it should run in the same thread as its antecedent.

I've searched the web but i only found the requirement for the thread to run in the SynchronizationContext, therefore i am starting to think I am missing some basic rule, specially regarding how Thread.Principal should work.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
Luis Filipe
  • 8,488
  • 7
  • 48
  • 76
  • 3
    Tying tasks to threads is a bad idea and error prone in case of exceptions. Instead of fixing the thread's principal and requiring all tasks to use the same thread, try passing the WindowsIDentity or token object to the tasks as state and impersonate the user in each task. Otherwise you run the risk of changing a ThreadPool's identity if an exception occurs and you forget to clear the identity – Panagiotis Kanavos Dec 28 '12 at 08:07
  • PS. What type of Identity are you using? WindowsIdentity or something else? – Panagiotis Kanavos Dec 28 '12 at 08:08
  • 1
    We implemented our own IPrincipal and it's the application itself that does the authentication. It seems that the best is to pass the IPrincipal amongst the tasks. – Luis Filipe Dec 28 '12 at 09:31

4 Answers4

17

First of all, don't use TaskContinuationOptions.ExecuteSynchronously for this purpose! You can't force the continuation on the same thread. It only works with very high probability. There are always cases where it does not work: Too much recursion will cause the TPL not to execute synchronously. Custom TaskSchedulers are also not obliged to support this.

This is a common misconception, especially because it is being wrongly propagated on the web. Here is some reading on that topic: http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx

If you need to run on the same thread, do this:

Task.Factory.StartNew(() => { First(); Second(); });

So easy.

Let me illustrate why that works by showing an alternative solution:

void MyCompositeTask()
{
  var result = First();
  Second(result);
}
Task.Factory.StartNew(() => MyCompositeTask());

This looks more intuitive: We pass MyCompositeTask to the TPL to run. The TPL does not care what we do in our callback. We can do whatever we want, including calling multiple methods and passing the results.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    No offense but I would take the authors of this book (considered by many to be the definitive reference on C# 4.0) to be a more reliable resource than you. Please provide some reference or reading material demonstrating how the solution you've provided ensures that the 2 methods will be called in a way that satisfies the behavior the OP is looking for. – Jesse Carter Dec 27 '12 at 16:51
  • I'm not arrogant enough to not admit that this is a more fitting solution but when you're telling me that the authors of a prestigious book on the subject are "wrong" I'd like to see a little more evidence. And at some point 99.999999% is good enough to be considered 100. The variance you're talking about in this case doesn't seem to be worth considering especially when deep recursion has no bearing here – Jesse Carter Dec 27 '12 at 16:53
  • 2
    @JesseCarter don't you think that executing one method after the other will guarantee that the principal persists? *Everything* persists between ordinary method invocations.; Deep recursion happens a lot with potentially infinite continuation chains.; Here are multiple scenarios where ExecSync does not come into effect: http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx (trusted source). – usr Dec 27 '12 at 16:55
5

From my C# textbook (C# 4.0 in a Nutshell):

You can force them [continuation tasks] to execute on the same thread [as their antecedent] by specifying TaskContinuationOptions.ExecuteSynchronously when calling ContinueWith: this can improve performance in very fine-grained continuations with lessening indirection.

In principal I haven't tried this but it seems to be what you're looking for and could be used in conjunction with Thread.CurrentPrincipal.

Here is a link to an MSDN article with some more concrete examples as well

Jesse Carter
  • 20,062
  • 7
  • 64
  • 101
  • 3
    You can't force it. It only works with very high probability! There are *always* cases where it does not work. Voted down because of that. – usr Dec 27 '12 at 16:46
  • 1
    Here's a reference for my previous statement: http://blogs.msdn.com/b/pfxteam/archive/2012/02/07/10265067.aspx – usr Dec 27 '12 at 16:56
5

Call the continuation with TaskScheduler.FromCurrentSynchronizationContext():

Task UITask= task.ContinueWith(() =>
{
 this.TextBlock1.Text = "Complete"; 
}, TaskScheduler.FromCurrentSynchronizationContext());

Copied from https://stackoverflow.com/a/4331287/503969

Community
  • 1
  • 1
Greg Sansom
  • 20,442
  • 6
  • 58
  • 76
  • "InvalidOperationException was unhandled: The current SynchronizationContext may not be used as a TaskScheduler." :-( – Ricibob Aug 19 '15 at 07:26
3

Setting a pool thread's identity is not a good idea. It ties you to this specific thread and and risks "leaking" the identity in case of exceptions, if you forget to clear the identity in an exception handler. You may end up with unrelated tasks running using the "leaked" identity.

Try passing the WindowsIdentity object to the tasks and impersonate using WindowsIdentity.Impersonate. This will allow you to use any available thread and will safely clear the identity even if an exception occurs.

You can try something like this:

WindowsPrincipal myPrincipal=...;
...
var identity=(WindowsIdentity)myPrincipal.Identity;
var task=Task.Factory.StartNew(ident=>{
        var id=(WindowsIdentity)ident;
        using(var context=id.Impersonate())
        {
            //Work using the impersonated identity here
        }
        return id;
    },identity).
.ContinueWith(r=>{
        var id = r.Result;
        using(var context=id.Impersonate())
        {
            //Work using the impersonated identity here
        }
});

The using statements ensure that the impersonated identity is cleared even if an exception occurs.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236