I have a long running SQL query inside a page that I've sped up by using an async task:
using System.Threading.Tasks;
...
var asyncTask = new Task<ResultClass>(
() =>
{
using (var stepAsync = MiniProfiler.Current.Step("Async!"))
{
// exec long running SQL
}
});
asyncTask.Start();
// do lots of other slow stuff
ResultClass result;
using (var stepWait = MiniProfiler.Current.Step("Wait for Async"))
{
result = asyncTask.Result;
}
(Note that this syntax will be a lot nicer once C# 5 comes out with async
and await
)
When using MVC mini profiler I get the timing for "Wait for Async", but I can't get the timing for the "Async!" step.
Is there any way to get those results (maybe just the SQL timings) into the trace for the completed page?
Update
I've found a way to get the profiler steps into the async method:
var asyncTask = new Task<ResultClass>(
profiler =>
{
using (var step = (profiler as MiniProfiler).Step("Async!"))
{
// exec long running SQL
}
}, MiniProfiler.Current);
That almost works, in that the "Async!" step appears (somewhat randomly, depending on the execution, and with some times appearing as negative) but isn't really what I want. The SQL timings and statements are still lost, and in this case they're the most valuable information.
Ideally I'd like the "Wait for Async" step to be linked to the timings (rather than the start step). Is there some way that stepWait
could be linked to the SQL profiler times for the result?
Any ideas?
What you can do is to create a new profiler and attach it to the web one.
The new profiler will have wrong times in its declaration but the steps are gonna be ok.
I've found a way to do this, by only keeping the SQL timings the main page steps still add up right:
This results in the SQL timings for the long running query being associated with the current step when the SQL completes. Typically this is the step waiting for the async result, but will be an earlier step if the SQL completes before I have to wait for this.
I've wrapped this in a dapper-style
QueryAsync<T>
extension method (always buffered and not supporting transactions) though it could do with a lot of tidy up. When I have more time I'll look into adding aProfiledTask<T>
or similar that allows the profiled results to be copied from the completed task.Update 1 (works in 1.9)
Following Sam's comment (see below) he's quite right:
AddSqlTiming
is not thread safe. So to get around that I've moved that to a synchronous continuation:This works in MvcMiniProfiler 1.9, but doesn't work in MiniProfiler 2...
Update 2: MiniProfiler >=2
The EF stuff added in version 2 breaks my hack above (it adds an internal-only
IsActive
flag), meaning that I needed a new approach: a new implementation ofBaseProfilerProvider
for async tasks:So then to use this provider I just need to start it when starting the task, and hook up the continuation synchronously (as before):
Now the SQL timings appear consistently against the step that initiated the async action. The "% in sql" is more than 100% though, that extra 82.4% is the time saved by doing the SQL in parallel.
Ideally I'd have the long running SQL query on the wait step rather than the init step, but I can't see a way to do that without changing the return type of the calling methods to explicitly pass around the timings (which would make the profiler considerably more obtrusive).