将MVC迷你探查器时序转换为异步任务

我在一个页面中运行了一个长时间的SQL查询,我通过使用异步任务来加速:

using System.Threading.Tasks; ... var asyncTask = new Task( () => { 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; } 

(注意,一旦C#5出现asyncawait ,这种语法会更好)

当使用MVC迷你探查器时,我得到了“等待异步”的时间,但我无法得到“异步!”的时间。 步。

有没有办法将这些结果(可能只是SQL时序)放入已完成页面的跟踪中?

更新

我找到了一种方法来让探查器步骤进入异步方法:

 var asyncTask = new Task( profiler => { using (var step = (profiler as MiniProfiler).Step("Async!")) { // exec long running SQL } }, MiniProfiler.Current); 

这几乎是有效的,因为“异步!” 步骤出现(有些随机,取决于执行,有时候显示为负),但实际上并不是我想要的。 SQL时序和语句仍然丢失,在这种情况下,它们是最有价值的信息。

理想情况下,我希望将“等待异步”步骤与时间(而不是开始步骤)相关联。 是否有某种方法可以将stepWait链接到结果的SQL事件探查器时间?

有任何想法吗?

我找到了一种方法来做到这一点,只需保持SQL时序主页面步骤仍然合适:

 var asyncTask = new Task( profiler => { 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 && 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); 

这导致SQL运行完成后,长时间运行的查询的SQL计时与当前步骤相关联。 通常,这是等待异步结果的步骤,但如果SQL在我必须等待之前完成,则将是更早的步骤。

我把它包装在一个QueryAsync扩展方法中(总是缓冲而不支持事务)虽然它可以做很多整理。 当我有更多时间时,我会考虑添加一个ProfiledTask或类似的东西,允许从已完成的任务中复制配置文件的结果。

更新1(适用于1.9)

根据Sam的评论(见下文),他说得对: AddSqlTiming不是线程安全的。 所以为了解决这个问题,我已将其转移到同步延续:

 // explicit result class for the first task class ProfiledResult { internal List 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>( () => { // 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(); 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( t => { // 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; 

这适用于MvcMiniProfiler 1.9,但在MiniProfiler 2中不起作用……

更新2:MiniProfiler> = 2

在版本2中添加的EF内容打破了我的hack(它添加了一个仅内部的IsActive标志),这意味着我需要一种新方法:用于异步任务的BaseProfilerProvider的新实现:

 public class TaskProfilerProvider : 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() { // 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> continuedTask) { return continuedTask.Result.SaveToParent(); } } 

那么使用这个提供程序我只需要在启动任务时启动它,并同步连接延续(如前所述):

 // Create a task that has its own profiler var asyncTask = new Task>( () => { // Use the provider to start a new MiniProfiler var result = new TaskProfilerProvider(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( TaskProfilerProvider.SaveToParent, TaskContinuationOptions.ExecuteSynchronously); asyncTask.Start(); return asyncWaiter; 

现在,SQL时序始终与启动异步操作的步骤一致。 但是, “%in sql”超过100%,额外的82.4%是通过并行执行SQL节省的时间。

  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 

理想情况下,我在等待步骤而不是init步骤上有长时间运行的SQL查询,但我无法在不改变调用方法的返回类型的情况下看到这样做的方法来显式传递时间(这会使剖析器更加突兀)。

你可以做的是创建一个新的探查器并将其附加到网络探测器。

 var newProfiler = new MiniProfiler("- Other task (discard this time)", ProfileLevel.Verbose); MiniProfiler.Current.AddProfilerResults(newProfiler); var asyncTask = new Task(() => { using (newProfiler.Step("Async!")) { Thread.Sleep(500); using (newProfiler.Step("Async 2!")) { Thread.Sleep(1000); } } }); asyncTask.Start(); 

新的探查器在声明中会有错误的时间,但步骤一切都会好的。