Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I've found a way to do this, by only keeping the SQL timings the main page steps still add up right:</p> <pre><code>var asyncTask = new Task&lt;T&gt;( profiler =&gt; { var currentProfiler = (profiler as MiniProfiler); // Create a new profiler just for this step, we're only going to use the SQL timings MiniProfiler newProfiler = null; if (currentProfiler != null) { newProfiler = new MiniProfiler("Async step", currentProfiler.Level); } using(var con = /* create new DB connection */) using(var profiledCon = new ProfiledDbConnection(con, newProfiler)) { // ### Do long running SQL stuff ### profiledCon.Query... } // If we have a profiler and a current step if (currentProfiler != null &amp;&amp; currentProfiler.Head != null) { // Add the SQL timings to the step that's active when the SQL completes var currentStep = currentProfiler.Head; foreach (var sqlTiming in newProfiler.GetSqlTimings()) { currentStep.AddSqlTiming(sqlTiming); } } return result; }, MiniProfiler.Current); </code></pre> <p>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.</p> <p>I've wrapped this in a dapper-style <code>QueryAsync&lt;T&gt;</code> 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 a <code>ProfiledTask&lt;T&gt;</code> or similar that allows the profiled results to be copied from the completed task.</p> <p><strong>Update 1 (works in 1.9)</strong></p> <p>Following Sam's comment (see below) he's quite right: <code>AddSqlTiming</code> is not thread safe. So to get around that I've moved that to a synchronous continuation:</p> <pre><code>// explicit result class for the first task class ProfiledResult&lt;T&gt; { internal List&lt;SqlTiming&gt; SqlTimings { get; set; } internal T Result { get; set; } } var currentStep = MiniProfiler.Current.Head; // Create a task that has its own profiler var asyncTask = new Task&lt;ProfiledResult&lt;T&gt;&gt;( () =&gt; { // Create a new profiler just for this step, we're only going to use the SQL timings var newProfiler = new MiniProfiler("Async step"); var result = new ProfiledResult&lt;T&gt;(); result.Result = // ### Do long running SQL stuff ### // Get the SQL timing results result.SqlTimings = newProfiler.GetSqlTimings(); return result; }); // When the task finishes continue on the main thread to add the SQL timings var asyncWaiter = asyncTask.ContinueWith&lt;T&gt;( t =&gt; { // Get the wrapped result and add the timings from SQL to the current step var completedResult = t.Result; foreach (var sqlTiming in completedResult.SqlTimings) { currentStep.AddSqlTiming(sqlTiming); } return completedResult.Result; }, TaskContinuationOptions.ExecuteSynchronously); asyncTask.Start(); return asyncWaiter; </code></pre> <p>This works in MvcMiniProfiler 1.9, but doesn't work in MiniProfiler 2...</p> <p><strong>Update 2: MiniProfiler &gt;=2</strong></p> <p>The EF stuff added in version 2 breaks my hack above (it adds an internal-only <code>IsActive</code> flag), meaning that I needed a new approach: a new implementation of <code>BaseProfilerProvider</code> for async tasks:</p> <pre><code>public class TaskProfilerProvider&lt;T&gt; : 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&lt;" + typeof(T).Name + "&gt;", 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() { // Add the timings from SQL to the current step var asyncProfiler = this.GetCurrentProfiler(); foreach (var sqlTiming in asyncProfiler.GetSqlTimings()) { this.step.AddSqlTiming(sqlTiming); } // Clear the results, they should have been copied to the main thread. this.Stop(true); return this.Result; } public static T SaveToParent(Task&lt;TaskProfilerProvider&lt;T&gt;&gt; continuedTask) { return continuedTask.Result.SaveToParent(); } } </code></pre> <p>So then to use this provider I just need to start it when starting the task, and hook up the continuation synchronously (as before):</p> <pre><code>// Create a task that has its own profiler var asyncTask = new Task&lt;TaskProfilerProvider&lt;T&gt;&gt;( () =&gt; { // Use the provider to start a new MiniProfiler var result = new TaskProfilerProvider&lt;T&gt;(currentStep); var asyncProfiler = result.Start(level); result.Result = // ### Do long running SQL stuff ### // Get the results return result; }); // When the task finishes continue on the main thread to add the SQL timings var asyncWaiter = asyncTask.ContinueWith&lt;T&gt;( TaskProfilerProvider&lt;T&gt;.SaveToParent, TaskContinuationOptions.ExecuteSynchronously); asyncTask.Start(); return asyncWaiter; </code></pre> <p>Now the SQL timings appear consistently against the step that initiated the async action. The <em>"% in sql"</em> is more than 100% though, that extra 82.4% is the time saved by doing the SQL in parallel.</p> <pre><code> 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 </code></pre> <p>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).</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload