Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top