Reputation: 2292
In my ASP.NET MVC application, I need to implement/override some intrinsic entities like IModelBinder.BindModel()
, IActionFilter.OnActionExecuting()
, etc. Those methods are calling an async method, but obviously I cannot turn them into async Task<>
to be able to use the await
keyword.
So I wrote the following "adapter":
public static T GetResult<T>(Func<Task<T>> func)
{
var httpContext = HttpContext.Current;
var proxyTask = Task.Run(() =>
{
HttpContext.Current = httpContext;
return func();
});
return proxyTask.Result;
}
which lets me call my async GetData()
in a synchronous manner:
public static async Task<Data> GetData()
{
if (isCached)
return GetCachedData();
var data = await GetOriginalData();
SetCachedData(data);
return data;
}
...
var data = GetResult(() => GetData());
Okay, it works, so I would call it a day, however as you can see, most of the time, GetData()
runs synchronously, therefore spawning a new thread unconditionally is not good performance-wise. That was bugging me, so I have ended up with a different solution:
public static T GetResult<T>(Func<Task<T>> func)
{
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
var task = func();
SynchronizationContext.SetSynchronizationContext(syncContext);
return task.Result;
}
It also works, however the issue is that since SynchronizationContext
is no longer flowing in, there is no HttpContext
available in the called method.
I could probably work that around by putting HttpContext.Current
into logical CallContext
but am still wondering if there is a better way to tackle the issue? Something like customizing SynchronizationContext.Current.CreateCopy()
.
Upvotes: 1
Views: 1151
Reputation: 456322
spawning a new thread unconditionally
Actually, just borrowing a thread briefly from the thread pool. Not nearly as bad as starting a new thread, but still not the best performance-wise.
It also works, however the issue is that since SynchronizationContext is no longer flowing in, there is no HttpContext available in the called method.
Are you sure? I would expect HttpContext.Current
to be set at the beginning of GetData
for both ways to call it. I would also expect HttpContext.Current
to be null
after the await
for both ways to call it. If it's not null
after await
in your testing, it's probably because the continuation just happened to end up on the same thread.
This is one of the main problems with trying to use HttpContext.Current
outside of the request context (SynchronizationContext
). When you place it on a bare thread (Task.Run
), it will just stay there until that thread enters another request context.
I could probably work that around by putting HttpContext.Current into logical CallContext
I would recommend pulling out whatever data you need from Current
while on the original request context, and then passing that data in explicitly to the methods that need it.
Regarding the original problem (avoiding the extra thread in the synchronous case), I'd recommend just adding a synchronous TryGetData
method.
Upvotes: 3