Kei*_*ith 7 c# asynchronous mvc-mini-profiler
我在页面中运行了一个长时间运行的SQL查询,我使用异步任务加速了:
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;
}
Run Code Online (Sandbox Code Playgroud)
(请注意,这句法将是一个要好很多,一旦C#5出来与async
和await
)
当使用MVC mini profiler时,我得到了"等待异步"的时间,但我无法得到"异步!"的时间.步.
有没有办法将这些结果(可能只是SQL时间)纳入已完成页面的跟踪中?
更新
我找到了一种方法来让探查器步骤进入异步方法:
var asyncTask = new Task<ResultClass>(
profiler =>
{
using (var step = (profiler as MiniProfiler).Step("Async!"))
{
// exec long running SQL
}
}, MiniProfiler.Current);
Run Code Online (Sandbox Code Playgroud)
这几乎是有效的,因为"异步!" 步骤出现(有些随机,取决于执行,有时候显示为负面),但实际上并不是我想要的.SQL时序和语句仍然丢失,在这种情况下,它们是最有价值的信息.
理想情况下,我希望将"等待异步"步骤与时间(而不是开始步骤)相关联.是否有某些方法stepWait
可以链接到结果的SQL事件探查器时间?
有任何想法吗?
Kei*_*ith 10
我找到了一种方法来做到这一点,只需保持主要页面步骤仍然正确的SQL时序:
var asyncTask = new Task<T>(
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);
Run Code Online (Sandbox Code Playgroud)
这导致SQL运行完成后,长时间运行的查询的SQL计时与当前步骤相关联.通常,这是等待异步结果的步骤,但如果SQL在我必须等待之前完成,则将是更早的步骤.
我把它包装在一个精巧的QueryAsync<T>
扩展方法(总是缓冲并且不支持事务),尽管它可以做很多整理.当我有更多时间时,我会考虑添加一个ProfiledTask<T>
或类似的,允许从已完成的任务中复制配置文件的结果.
更新1(适用于1.9)
根据Sam的评论(见下文),他说得很对:AddSqlTiming
不是线程安全的.所以为了解决这个问题,我已将其转移到同步延续:
// explicit result class for the first task
class ProfiledResult<T>
{
internal List<SqlTiming> 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<ProfiledResult<T>>(
() =>
{
// 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<T>();
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>(
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;
Run Code Online (Sandbox Code Playgroud)
这适用于MvcMiniProfiler 1.9,但在MiniProfiler 2中不起作用......
更新2:MiniProfiler> = 2
在版本2中添加的EF内容打破了我的hack(它添加了一个仅内部IsActive
标志),这意味着我需要一种新的方法:BaseProfilerProvider
异步任务的新实现:
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()
{
// 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<TaskProfilerProvider<T>> continuedTask)
{
return continuedTask.Result.SaveToParent();
}
}
Run Code Online (Sandbox Code Playgroud)
那么使用这个提供程序我只需要在启动任务时启动它,并同步连接延续(如前所述):
// 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 = // ### 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<T>(
TaskProfilerProvider<T>.SaveToParent,
TaskContinuationOptions.ExecuteSynchronously);
asyncTask.Start();
return asyncWaiter;
Run Code Online (Sandbox Code Playgroud)
现在,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
Run Code Online (Sandbox Code Playgroud)
理想情况下,我在等待步骤而不是init步骤上有长时间运行的SQL查询,但是我无法在不改变调用方法的返回类型的情况下看到一种方法来显式传递时间(这将使得剖析器更加突兀).