I found a way to do this, only saving SQL timeouts, the main page steps still stack correctly:
var asyncTask = new Task<T>( profiler => { var currentProfiler = (profiler as MiniProfiler);
As a result, the SQL timings for a long query are related to the current step at the end of SQL. This is usually a step that expects an async result, but will be an earlier step if SQL completes before I have to wait for it.
I wrapped this in a QueryAsync<T>
-style QueryAsync<T>
extension method (always buffered and transactionless), although it could have done with a lot of accuracy. When I have more time, I will consider adding ProfiledTask<T>
or the like, which allows you to copy profiled results from a completed task.
Update 1 (works in version 1.9)
After Sam's comment (see below), he is completely right: AddSqlTiming
not thread safe. Therefore, to get around this, I moved this to a synchronous continuation:
This works in MvcMiniProfiler 1.9, but does not work in MiniProfiler 2 ...
Update 2: MiniProfiler> = 2
The EF element added in version 2 breaks my hack above (it adds the internal IsActive
flag), which means that I need a new approach: a new implementation of BaseProfilerProvider
for async tasks:
public class TaskProfilerProvider<T> : BaseProfilerProvider { Timing step; MiniProfiler asyncProfiler; public TaskProfilerProvider(Timing parentStep) { this.step = parentStep; } internal T Result { get; set; } public override MiniProfiler GetCurrentProfiler() { return this.asyncProfiler; } public override MiniProfiler Start(ProfileLevel level) { var result = new MiniProfiler("TaskProfilerProvider<" + typeof(T).Name + ">", level); this.asyncProfiler = result; BaseProfilerProvider.SetProfilerActive(result); return result; } public override void Stop(bool discardResults) { if (this.asyncProfiler == null) { return; } if (!BaseProfilerProvider.StopProfiler(this.asyncProfiler)) { return; } if (discardResults) { this.asyncProfiler = null; return; } BaseProfilerProvider.SaveProfiler(this.asyncProfiler); } public T SaveToParent() {
So, to use this provider, I just need to run it when the task starts and connect the continuation synchronously (as before):
// Create a task that has its own profiler var asyncTask = new Task<TaskProfilerProvider<T>>( () => { // Use the provider to start a new MiniProfiler var result = new TaskProfilerProvider<T>(currentStep); var asyncProfiler = result.Start(level); result.Result = //
SQL timings are now displayed sequentially in the step that triggered the async action. "% In sql" is over 100%, although an additional 82.4% is the time that can be saved by running SQL in parallel.
duration (ms) from start (ms) query time (ms) Start ...Async 0.0 +19.0 1 sql 4533.0 Wait for ...Async 4132.3 +421.3 182.4 % in sql
Ideally, I will have a long SQL query at the wait step, and not at the init step, but I see no way to do this without changing the return type of the calling methods to explicitly pass the timings (which would make the profiler much more intrusive).