gpt4 book ai didi

c# - 将 MVC mini-profiler 时间安排到异步任务中

转载 作者:太空狗 更新时间:2023-10-29 20:03:29 25 4
gpt4 key购买 nike

我在一个页面中有一个长时间运行的 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;
}

(请注意,一旦 C# 5 推出 asyncawait,此语法会好很多)

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

有什么方法可以将这些结果(可能只是 SQL 计时)放入已完成页面的跟踪中吗?

更新

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

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

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

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

有什么想法吗?

最佳答案

我找到了一种方法来做到这一点,只需保持主页步骤的 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);

这会导致长时间运行的查询的 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;

这在 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();
}
}

那么为了使用这个提供者,我只需要在开始任务时启动它,并同步连接继续(和以前一样):

// 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;

现在,SQL 时间显示与启动异步操作的步骤一致。 “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

理想情况下,我会在等待步骤而不是初始化步骤上进行长时间运行的 SQL 查询,但如果不更改调用方法的返回类型以显式传递计时,我看不出有什么方法可以做到这一点(这将使探查器更加突兀)。

关于c# - 将 MVC mini-profiler 时间安排到异步任务中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8341269/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com