Thread Static, ASP.NET and Async handlers
Asked Answered
B

1

6

Please consider these sceanrios:

  1. An async .ashx handler
  2. A async .asmx web-service method
  3. A sync MVC 5 controller action method

I am trying to figure out a way to set "logical thread" specific data that can be accessed consistently during a "logical" http request, i.e. if the data was set on the thread in the "BeginExecute" part of which-ever async handler you would consider, that data is available in the "EndExecute" part of that asnc handler even if ASP.NET executes the "EndExecute" part on a different OS/.Net thread.

Moreover, I am expecting that the data set in the "BeginExecute" part on whatever OS/.Net thread it was on is NOT available on a subsequent http request if the second request is assigned the thread that was earlier assigned to first http request when it was in "BeginExecute" portion but this thread freed up as the first http request went in its async operation (and its possibly still completing its async operation).

I believe the word "logical thread" or "logical thread context" in .Net actually means the same "logical" flow of operation that I have mentioned (and not the underlying OS/.Net thread that keeps getting re-assigned). If you look at it from a workflow perspective, each http request is a new "logical" operation (even if multiple users invoke the same web-service sequentially or in parallel, each request is a new and separte logical operation), and in this meaning, the "logical" operation is one-time and cannot repeat. However the same underlying OS/.Net threads can be mapped to "logical" operations as they arrive based on their availability.

Additionally I want to expose this data as HttpContext.Current sort of static property. To some people this may come as a surprise, but HttpContext.Current does not work correctly if you are using for example async .asmx web-service methods. I am sure I have read content on web which says HttpContext.Current should always return correct HttpContext, but I have seen it as null in EndExecuteMethod of .asmx web-methods. It would be great if somecan can confirm if I am right in making my last statement, but this statement is not the overall question I am trying to ask here.

After reading a good amount of literature (e.g. What is the difference between log4net.ThreadContext and log4net.LogicalThreadContext?, http://msmvps.com/blogs/jon_skeet/archive/2010/11/08/the-importance-of-context-and-a-question-of-explicitness.aspx, http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html and more including MSDN docs), here are my inferences:

  1. ThreadStatic is local to underlying OS/.Net thread and not to the "logical" operation, hence in my example; data set on first http request in "BeginExecute" would be visible in next http request if the second http request gets assigned the same thread as "BeginExecute" for first thread. And this data won't be available in "EndExecute" if it happens to be re-assigned to another thread by .Net (which would happen in vast majority of the cases).
  2. Thread.SetData is even more problematic for my use-case. It needs data slots to be passed in and if I were to pass in a data slot from a return value of Thread.GetNamedDataSlot, the information is available across the app domain; as named data slots are shared between threads.
  3. CallContext.SetData is like ThreadStatic (which means its not shared by app domain but different http requests would see the same data if they get assgined to the same underlying OS/.Net thread). CallContext.SetData provides an additional ability to marshal the context data for RPC calls which is irrelevant to the current question being asked.
  4. Then there's the ThreadLocal class (.Net 4/.Net 4.5). It could have solved one part of my problem it seems, I could have passed it inside stateObject of BeingExecute operation, and extract from the same stateObject parameter of endExecute operation. From this perspective, ThreadLocal seems to be written for .Net's async support. But it won't work when I need to access it like HttpContext.Current as there's no way I can see to preserve the "logical thread static" instance ofit (unless I have said something incorrectly in my previous 3 points).
  5. And finally it seems CallContext.LogicalSetData does what I intend to achive. Using the set of CallContext.LogicalSetData and CallContext.LogicalGetData methods, I should be able to achieve the HttpContext.Current like impact which works correctly for "logical task executions".

Now come the questions:

  1. Is everything I have said above correct. Please correct any and all incorrect claims I have made.
  2. Are there any other options available for thread static kind of feature in .Net that I missed.
  3. Does CallContext.LogicalSetData/LogicalGetData pass on the context data to RPC calls (the msdn page does not mention clearly, http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.callcontext.logicalsetdata(v=vs.110).aspx).
  4. Are there any downsides (performance wise or otherwise) of using CallContext.LogicalSetData/LogicalGetData.
  5. This page says something about copy-on-write behavior for LogicalSetData: http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html. In the context of async handlers/async MVC 5 action methods, what's the impact if I save a reference type using logicalsetdata and later change the state of the reference type. What are the repuccursions.
  6. For mutation/logicalsetdata/async, I still can't see what's the problem by mutating the object. When the async method starts, the copy-on-write behavior would trigger a copy of context data the next time logicalsetdata is called. This is a shallow copy, so my reference object is now actually shared by 2 logical contexts and the changes in one context are visible in the other context which is what I would normally expect from a reference type.

A long question with lots of references, but hopefully I did my research well and the answers would benefit other people too.

Backus answered 15/5, 2014 at 21:24 Comment(0)
M
0

I am trying to figure out a way to set "logical thread" specific data that can be accessed consistently during a "logical" http request

The only possible options are HttpContext.Current.Items and the logical CallContext.

Moreover, I am expecting that the data set in the "BeginExecute" part on whatever OS/.Net thread it was on is NOT available on a subsequent http request

HttpContext.Current.Items will always be cleared on a new request, but you'll have to clear the logical CallContext data yourself.

HttpContext.Current does not work correctly if you are using for example async .asmx web-service methods.

I find this surprising. I haven't tried it, but it should work - if you are running on .NET 4.5, targeting .NET 4.5 (i.e., have targetFramework set to 4.5 in your web.config), and aren't using async void.

[ThreadStatic], thread-local data slots, (non-logical) CallContext, and ThreadLocal are all thread-specific data, and will not work for asynchronous code.

Is everything I have said above correct. Please correct any and all incorrect claims I have made.

There is really way too much text in your question. Stack Overflow is a Q&A site, not a mentoring site.

Are there any other options available for thread static kind of feature in .Net that I missed.

No.

Does CallContext.LogicalSetData/LogicalGetData pass on the context data to RPC calls

I have no idea. Try it and see.

Are there any downsides (performance wise or otherwise) of using CallContext.LogicalSetData/LogicalGetData.

There's a definite performance hit. The .NET framework is highly optimized for the common case (no logical call context data).

what's the impact if I save a reference type using logicalsetdata and later change the state of the reference type.

The logical CallContext has shallow-copy-on-write behavior. So, any kind of asynchronous fork/join concurrency (i.e., Task.WhenAll) will end up sharing that state. If you use ConfigureAwait(false), you could also end up with race conditions.

To actually solve your problem, I recommend you first look into why HttpContext.Current doesn't work as expected; my guess (without seeing the project) is that targetFramework is set to 4.0 instead of 4.5. HttpContext.Current.Items is the most performant choice if you can get it working.

Marlea answered 26/5, 2014 at 3:8 Comment(3)
Hi Stephen, thanks for your helpful pointers. One of the reasons I am not trying to use HttpContext.Current is its not amicable for unit testing. I think I can manage with either of ThreadStatic or Logical call contexts ensuring I clear data from previous requests on each new request.Backus
With all due respect, I would also like to counter the point of "mentoring site". I tried to research well enough before asking. Sure the question was long, I could have split it to multiple questions, one for each available options. But I believe it was indeed a question and not trying to have someone find answers for me. I could not find direct answers on web to most of the questions raised. And writing test cases to verify some of these points isn't going to work, some of these needed to be confirmed at theoretical level and cannot be captured in test cases.Backus
Trying to understand the reflected code also does not help because of the internal (non-documented) classes, factory patterns and under nuisances where you can't always go-back and forth to understand the relationship between classes and work-flow. Though I would like to thank you again, your replies cleared most of the points I had.Backus

© 2022 - 2024 — McMap. All rights reserved.