16

How can something like a ThreadStatic be used in a TPL Task? My understanding ("Wrox Professional Parallel Programming with C#", p74) is that a Task can switch from one thread to another during execution.

What I want to do?

I want to maintain a session id inside a static class so I don't need to pass this id to all of my methods. My library has methods like login(id), logout(id) and many methods which operate on credentials associated with this id. But I don't want to pass this id to every method. I can make sure my library is called within different thread for different sessions. So saving the id inside login() in a ThreadStatic variable will work.

Now I want to use TPL Tasks which are created for me by a ThreadPool. I can pass my session id to the Task, but if I store this id inside a ThreadStatic variable it will not survive if my Task switches threads.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
Gerard
  • 161
  • 3
  • 2
    I haven't heard of an actual *task* switching from one thread to another during execution. Where did you hear about that as a possibility? – Jon Skeet Aug 01 '11 at 09:48
  • 2
    There is a figure on page 74 of the book "Wrox Professional Parallel Programming with C#" showing a Task switching between worker threads. – Gerard Aug 01 '11 at 10:04
  • 1
    @Gerard: Are you sure that's *while executing* rather than the task going in the *queue* for one thread, and then work stealing meaning it switches over to another thread for execution? – Jon Skeet Aug 01 '11 at 10:34
  • 1
    The figure shows clearly a longer running task which switches threads during execution. I also couldn't find any other resources stating the thread switching theory. I was hoping this figure is wrong and a startet Task is definately attached to a thread. – Gerard Aug 01 '11 at 10:44
  • I agree that even if there is the possibility of migrating a task between threads, it is very unlikely that this is implemented: it would be too costly to do. Even if this is the case, are you comfortable with the idea of sharing the same ID between different tasks? Very likely, tasks will multiplex on the same thread. The solution you mentioned (passing the ID to every method) seems like the cleanest one to me. – Lorenzo Dematté Aug 10 '11 at 08:13

1 Answers1

5

TPL and .Net 4.5's async flow the ExecutionContext, which means you can use CallContext.LogicalSetData(string, object) and CallContext.GetLogicalData(string) in much the same way you would use ThreadStatic. It does incur a significant performance penalty, however. This has been exposed in .Net 4.6 and up (including .Net Standard 1.3 and up) with the AsyncLocal<> wrapper.

See Async Causality Chain Tracking, How to include own data in ExecutionContext, and ExecutionContext vs SynchronizationContext for a deeper dive.

Example use:

class Program
{
    static async void Main(string[] args)
    {
        Logger.Current = new Logger("Test Printer");

        Logger.Current.Print("hello from main");
        await Task.Run(() => Logger.Current.Print($"hello from thread {Thread.CurrentThread.ManagedThreadId}"));
        await Task.Run(() => Logger.Current.Print($"hello from thread {Thread.CurrentThread.ManagedThreadId}"));
    }
}

class Logger
{
    private string LogName;

    public Logger(string logName)
    {
        if (logName == null)
            throw new InvalidOperationException();

        this.LogName = logName;
    }

    public void Print(string text)
    {
        Console.WriteLine(LogName + ": " + text);
    }

    private static AsyncLocal<Logger> _logger = new AsyncLocal<Logger>();
    public static Logger Current
    {
        get => _logger.Value;
        set => _logger.Value = value;
        }
    }
}

Prints:

Test Printer: hello from main  
Test Printer: hello from thread 11 
Test Printer: hello from thread 10
Mitch
  • 21,223
  • 6
  • 63
  • 86
  • Unfortunately, not supported on .NET Standard. Besides, I would hesitate to add anything from the `System.Runtime.Remoting` namespace to any new project these days. – NightOwl888 Aug 25 '19 at 10:12
  • @NightOwl888, they have been exposed by newer frameworks as `AsyncLocal`. I don't think this functionality is going away any time soon (given the myriad framework features dependent upon it). Answer updated. – Mitch Aug 25 '19 at 15:51