4

[ThreadStatic] is used in various places in the .NET framework to provide an ambient context for various features (e.g. Transaction.Current, which is used for TransactionScope).

Unfortunately, this means that features which do some thread juggling (ASP.NET, async keyword code) switch threads, but don't copy the TransactionScope, so features like TransactionScope don't work as you might expect.

There is another mechanism, CallContext.LogicalGetData (more here) which does copy across state during thread switches correctly (at least in .NET 4.5). It seems to me that TransactionScope would be better if it used this rather than [ThreadStatic].

If the features that are using [ThreadStatic] were written today, rather than being existing features with requirements of backwards compatability, would they be written using CallContext.(G|S)etLogicalData?

mcintyre321
  • 12,996
  • 8
  • 66
  • 103
  • There are some kinds of operations which are inherently sequential, and use of thread-static storage can be cleaner than having to have code outside a method pass parameters for anything the code inside the method might be interested in. Having things be bound to a thread can be problematic with things like async code; it would be nice if a framework had first-class support for a one-way method by which methods could pass supplemental information to nested methods they call, without intervening layers having to handle it. Async methods could then... – supercat Mar 08 '14 at 22:02
  • ...be given a read-only reference to the environment in which they were invoked; if async methods need to pass data to further nested methods, they could construct a new environment with a link to the parent. – supercat Mar 08 '14 at 22:04

2 Answers2

1

Yes. I'm sure it seemed like a good idea at the time, but [ThreadStatic] lulls developers into a false sense of security. ThreadStatic fields have many of the drawbacks of global variables, and the sole advantage (different threads have their own instances of the global thing) has a corresponding disadvantage (that thing disappears if you switch threads). This is not a win. Globals suck, and thread-static globals suck every bit as much as the regular kind.

I work on a rather large code base, several years old with dozens of developers (probably a couple hundred if you could people who have left the team over the last several years), and [ThreadStatic] bites us on a regular basis. We've got tons of code that, under-the-covers, uses a ThreadStatic global, which of course breaks if you do anything on a worker thread. Like I said, I'm sure it seemed like a good idea at the time... but it has now cost us much more than it ever bought us.

The alternative would be to pass around the same object (HttpContext, Transaction, etc) to every method that depends on it. That requires more typing, but in my not-so-humble opinion it is still better in the long run.

NSFW
  • 557
  • 4
  • 12
1

In reality they have very different use cases.

  • ThreadStatic can't transfer a value across an await or similar context switch.
  • CallContext can't retain a value per-thread.

So you see, one can not replace the other. ThreadStatic is a low-level primitive. I don't think its use cases have gotten any fewer after CallContext etc. came along. Note, its use cases are vanishingly small -- I think the last time I used it was probably over two years ago.

I would characterize things like Transaction.Current as an abuse of TLS. It was never designed for this, and so when TLS appeared to break async, it was only because it never should have been used for that in the first place.

Cory Nelson
  • 29,236
  • 5
  • 72
  • 110