0

I'm looking for an execution context which plays nicely with async/await and with the TPL at the same time in the following way (expected behavior):

async Task<string> ReadContext(string slot)
{
    // Perform some async code
    ...
    return Context.Read(slot);
}

(1) Playing nicely with async/await

Context.Store("slot", "value");
await DoSomeAsync();
Assert.AreEqual("value", Context.Read("slot"));

Context.Store("slot", "value");
var value = await ReadContext("slot");
Assert.AreEqual("value", value);

(2) Playing nicely with Task.Run()

Context.Store("slot", "value");
var task = Task.Run(() => ReadContext("slot"));
Assert.IsNull(task.Result);

(3) Playing nicely with an awaited Task

Context.Store("slot", "value");
var value = await Task.Run(() => ReadContext("slot"));
Assert.AreEqual("value", value);

(3) is not essential but would be nice. I use CallContext now but it fails at (2) as the values stored in it are accessible even in manually ran tasks, even in those ran using Task.Factory.StartNew(..., LongRunning) which should enforce running the task on a separate thread.

Is there any way to accomplish that?

twoflower
  • 6,588
  • 2
  • 31
  • 42
  • Doesn't look like a normal [Execution Ctx](http://msdn.microsoft.com/en-us/library/system.threading.executioncontext.aspx) to me. Can you elaborate? – Henk Holterman Oct 16 '13 at 09:05
  • I don't understand `Assert.IsNull` at **(2)** (which is the blocking wait on task.Result). Did you mean something like `Assert.IsNull(task.Result, "should be null")` ? – noseratio Oct 16 '13 at 09:13
  • Basically, I need a place to store NHibernate sessions in ASP.NET application. `HttpContext` works fine (and respects `async/await`) if I'm inside a request context, but it is unavailable once I jump into manually ran tasks. Anyway, now I realized what I want is probably impossible since when I start a task manually, the environment does not know whether I'm gonna await it or not. – twoflower Oct 16 '13 at 09:17
  • @Noseratio You're right, edited. – twoflower Oct 16 '13 at 09:17
  • 1
    I'm surprised that `CallContext` is passed over to a pool thread via `Task.Run` like with **(2)**, are you sure? Anyway, now the requirements for **(2)** and **(3)** look mutually exclusive to me, if I'm not missing anything. – noseratio Oct 16 '13 at 09:22
  • @Noseratio I was surprised as well. As to the mutual exclusivity of __(2)__ and __(3)__, you're probably right (unfortunately for me) – twoflower Oct 16 '13 at 09:39

1 Answers1

4

Your real question is in your comment:

I need a place to store NHibernate sessions in ASP.NET application. HttpContext works fine (and respects async/await) if I'm inside a request context, but it is unavailable once I jump into manually ran tasks.

First off, you should be avoiding "manually run tasks" in an ASP.NET application at all; I have a blog post on the subject.

Secondly, storing things in HttpContext.Items is sort of a hack. It can be useful in a handful of situations, but IMO managing NHibernate sessions is something that should be designed properly into your application. That means you should be passing around the session (or a service providing access to the session) either in your method calls or injected into each type that needs it.

So, I really think that a "context" like you're looking for is a wrong solution. Even if it were possible, which it is not.

As @Noseratio noted, requirements (2) and (3) cannot both be met. Either the code executing in the Task.Run has access or it does not; it can't be both.

As you've discovered, requirements (1) and (3) can be met by the logical call context (note to Googlers: this only works under .NET 4.5 and only if you store immutable data; details on my blog).

There is no easy way to satisfy (1) and (2) unless you manually remove the data (FreeNamedDataSlot) at the beginning of the code in Task.Run. I think there may be another solution but it would require custom awaitables at every await, which is completely cumbersome, brittle, and unmaintainable.

Stephen Cleary
  • 376,315
  • 69
  • 600
  • 728
  • Stephen, I agree that passing the session from the top all the way down is the best solution. However, IMHO, it has a big disadvantage that you have to use this approach _everywhere_. You can't "relax" this requirement a little without having to come up with some kind of context. – twoflower Oct 16 '13 at 13:09
  • As for not manually running tasks at all - this I have to do in cases where the operation takes long to process and the HTTP request cannot wait for it to finish (which I think is generally a good practice). Yes, I've read [your article](http://blog.stephencleary.com/2012/12/returning-early-from-aspnet-requests.html) :-) – twoflower Oct 16 '13 at 13:12