2

Say, I have the following code:

IPrincipal capturedPrincipal = Thread.CurrentPrincipal;
myseq.AsParallel().Select(x =>
{
    Thread.CurrenctPrincipal = capturedPrincipal;
    /*call code protected with CAS*/
});

to be sure Thread.CurrenctPrincipal will be propagated to every thread where Select's delegate will be executed on. I've realized that if I had proper SynchronizationContext set up this would happen automatically. So does PLINQ use SynchronizationContext when queuing work items on ThreadPool? And if no, why?

p.s.

I think it's important to note that the code above is executed in WCF environment hosted under IIS/WAS (no ASP.NET compatibility).

Edit: Found a similar question confirming same behavior I witness.

Edit2: Modified casperOne's test a bit and it fails saying threadid is the same:

    [Test]
    public void Test1()
    {
        var principal = new GenericPrincipal(new GenericIdentity("test"), new string[0]);
        Thread.CurrentPrincipal = principal;
        int threadID = Thread.CurrentThread.ManagedThreadId;

        Enumerable.Range(0, 4000).AsParallel()
            .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
            .Select(x =>
                {
                    Assert.AreSame(Thread.CurrentPrincipal, principal);
                    Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadID);
                    return x;
                })
            .ToArray();
    }
Community
  • 1
  • 1
UserControl
  • 14,766
  • 20
  • 100
  • 187
  • 1
    It looks like you're more interested in the ExecutionContext than the SynchronizationContext here. – Jon Skeet Oct 24 '12 at 11:52
  • As far as I know ExecutionContext is kind of internal framework stuff and should not be used directly. I would be happy if ThreadPool had events to subscribe for when work items are being queued but that's not the case :( – UserControl Oct 24 '12 at 11:55
  • I'm not suggesting that you should be fiddling with `ExecutionContext` yourself - I'm just saying that it's the `ExecutionContext` that's important here, not the `SynchronizationContext`. (It's probably really a context *within* `ExecutionContext`, but `SynchronizationContext` is all about what thread code runs on, not who it runs *as*.) – Jon Skeet Oct 24 '12 at 12:00
  • Yes, I got your point. But if I'm not mistaken `AspNetSynchronizationContext` does flow thread's cultures and they are part of `ExecutionContext`. And so is `CurrentPrincipal`, right? – UserControl Oct 24 '12 at 12:04
  • I don't know about AspNetSynchronizationContext but it's not the same as SynchronizationContext. – Jon Skeet Oct 24 '12 at 18:05
  • If you reproduce the code *exactly* same results? – casperOne Oct 24 '12 at 20:04
  • Without modifications your test is green. But what's the point to test on a single input? – UserControl Oct 24 '12 at 20:06
  • @UserControl Your's fails if you have a single input as well. There's no point in testing multiple inputs, as we're testing the behavior of PLINQ; it doesn't matter how many items in the sequence are used. – casperOne Oct 24 '12 at 20:15
  • I think it does. Because a number of work items depends on partitioning PLINQ does. And I think there is difference between when a thread is ready in `ThreadPool` or it needs to create a new one. Btw, did you try my test on 4000 input? – UserControl Oct 24 '12 at 20:22
  • @UserControl Ahh, I see, apparently, `ForceParllelism` is a suggestion at best. The 4000 case fails as well. Remember, the issue is whether or not the principal is brought over to other threads. I've updated the answer to reflect the test we should be running to reflect that, and to determine whether or not any parallelization took place (and to only succeed when there is *some* parallelization). – casperOne Oct 24 '12 at 20:42
  • So that means PLINQ utilizes the main thread as well. Very smart. As for `CurrentPrincipal` I need to perform additional testing in my code. Perhaps hosting environment makes the difference. Thanks for your efforts! – UserControl Oct 24 '12 at 20:53
  • @UserControl There's no reason for it *not* to, we just had to bump it up to make sure it *does* and then check the cases when it does. Glad it's working out. – casperOne Oct 24 '12 at 20:57
  • It's not clear from this thread: what's the answer to the very first snippet (when forcibly setting Thread.CurrentPrincipal)? From the snippen Edit2 it seems Principal is flowing just fine? (In my situation i'm only getting correct principal during the first iteration, then there appears some other one). – esteewhy Feb 04 '15 at 13:12

1 Answers1

3

There are two questions here. The first is whether or not Thread.CurrentPrincipal is propagated to threads in PLINQ.

The answer is yes, as this is part of the ExecutionContext and the ExecutionContext is captured from the calling thread and copied to the new/recycled thread when a new thread/task/thread pool thread is started.

The following test case (run in .NET 4.0) shows this:

[TestMethod]
public void TestMethod1()
{
    // Capture the current logged in account.
    // Could be a GenericPrincipal as well with some random value
    // set on the identity name.
    IPrincipal p = new WindowsPrincipal(WindowsIdentity.GetCurrent());

    // Set the current principal.
    Thread.CurrentPrincipal = p;

    // Set the synchronization context.
    SynchronizationContext.SetSynchronizationContext(
        new SynchronizationContext());

    // Context is not null.
    Assert.IsNotNull(SynchronizationContext.Current);

    // PLINQ.
    var plinqThreadDetails = 
        // Go parallel.  This number needs to be reasonably
        // high to force parallelization as PLINQ might
        // use this thread if the size is small.
        from i in Enumerable.Range(0, 4000).AsParallel().
            // Force parallelization.  At best, this is
            // a suggestion.
            WithExecutionMode(ParallelExecutionMode.ForceParallelism)
        select new {
            // These values are retreived on another thread.
            IdentityName = Thread.CurrentPrincipal.Identity.Name,
            Thread.CurrentThread.ManagedThreadId,
        };

    // Was there any parallelization?
    bool anyParallel = false;

    // Make assertions.
    // The managed thread id is different than the current one.
    foreach (var plinqThreadDetail in plinqThreadDetails)
    { 
        // But the principal still flowed, even though on a different
        // thread.
        Assert.AreEqual(Thread.CurrentPrincipal.Identity.Name,
            plinqThreadDetail.IdentityName);

        // Update any parallel.
        anyParallel |= (plinqThreadDetail.ManagedThreadId !=
            Thread.CurrentThread.ManagedThreadId);
    }

    // There was *some* parallelization.
    Assert.IsTrue(anyParallel);
}

Regarding whether or not SynchronizationContext is used in PLINQ, it's not, and it doesn't make sense to.

Considering that using a SynchronizationContext usually means serializing a call to a particular context (which is usually a thread, think UI applications, but not always, given the ASP.NET synchronization context), you'd kill any gains that PLINQ would gain from parallelization because every call would have to be marshaled back through the SynchronizationContext.

The benefits in PLINQ come from being able to execute these operations at the same time, not one-at-a-time.

The following test case (very much along the lines of the previous one) proves that the SynchronizationContext is not captured for PLINQ threads:

[TestMethod]
public void TestMethod2()
{
    // Set the synchronization context.
    SynchronizationContext.SetSynchronizationContext(
        new SynchronizationContext());

    // Context is not null.
    Assert.IsNotNull(SynchronizationContext.Current);

    // PLINQ.
    var plinqThreadDetails = 
        // Go parallel.  This number needs to be reasonably
        // high to force parallelization as PLINQ might
        // use this thread if the size is small.
        from i in Enumerable.Range(0, 4000).AsParallel().
            // Force parallelization.
            WithExecutionMode(ParallelExecutionMode.ForceParallelism)
        select new {
            // These values are retreived on another thread.
            SynchronizationContextIsNull =
                SynchronizationContext.Current == null,
            Thread.CurrentThread.ManagedThreadId,
        };

    // Make assertions.
    // Was there any parallelization?
    bool anyParallel = false;

    // Make assertions.
    // The synchronization context on the PLINQ thread was
    // not set, only if on a different thread.
    foreach (var plinqThreadDetail in plinqThreadDetails)
    {
        // If the thread id is different.
        if (plinqThreadDetail.ManagedThreadId !=
            Thread.CurrentThread.ManagedThreadId)
        {
            // The synchronization context was null.
            Assert.IsTrue(plinqThreadDetail.SynchronizationContextIsNull);

            // There was something on another thread.
            anyParallel = true;
        }
        else
        {
            // The synchronization context is not null.
            Assert.IsFalse(plinqThreadDetail.SynchronizationContextIsNull);
        }
    }

    // There was *some* parallelization.
    Assert.IsTrue(anyParallel);
}
casperOne
  • 73,706
  • 19
  • 184
  • 253
  • *ExecutionContext is captured from the calling thread and copied to the new/recycled thread when a new thread/task/thread pool thread is started.*. I do not think it is true, that's why I do it manually. Otherwise my code fails because I get random principals within the delegate. Do you have a link to official MSDN article on that? – UserControl Oct 24 '12 at 13:06
  • @UserControl See the sixth paragraph (right under the code sample) of the MSDN referenced article (linked from "Execution context is captured from..."). The blog post is from Stephen Toub, who is on (if not managing) the PFX team, which is the parallelization team. – casperOne Oct 24 '12 at 13:09
  • Updated my question with a link to similar problem. Looks like something is really broken with `ExecutionContext`. – UserControl Oct 24 '12 at 13:31
  • @UserControl Updated my answer; the test case I've written shows that `ExecutionContext` is indeed flowed in PLINQ. – casperOne Oct 24 '12 at 13:47
  • Also, are you *really* using `Thread.CurrentPrincipal` or `HttpContext.User`? They are different and not interchangeable. – casperOne Oct 24 '12 at 13:49