C# 将MVC迷你探查器计时引入异步任务

C# 将MVC迷你探查器计时引入异步任务,c#,asynchronous,mvc-mini-profiler,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 SQ

我在一个页面中有一个长时间运行的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;
}
这几乎是可行的,因为“Async!”步骤出现了(根据执行情况,有些是随机的,有些时候显示为负数),但这并不是我真正想要的。SQL计时和语句仍然丢失,在这种情况下,它们是最有价值的信息

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


有什么想法吗?

您可以做的是创建一个新的探查器并将其连接到web探查器

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();

新的探查器在其声明中将有错误的时间,但步骤将是正确的。

我找到了一种方法来做到这一点,只需保持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);
这在MvcMiniProfiler 1.9中有效,但在MiniProfiler 2中不起作用

更新2:MiniProfiler>=2

版本2中添加的EF东西打破了我的上述缺陷(它添加了一个仅限内部的
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();
    }
}

理想情况下,我会在等待步骤而不是init步骤上使用长时间运行的SQL查询,但如果不更改调用方法的返回类型以显式传递计时(这将使探查器更加突出),我无法找到这样做的方法。

,您是否尝试过在异步任务中定义一个新的分析上下文,然后您可以获得一个全新的时间安排。@SamSaffron-干杯,我来试试。@SamSaffron-好的,尝试过(使用类似于Andres下面的答案,但在任务的lambda中生成新上下文)。它获得正确的SQL计时,但同时报告多个异步步骤,所有步骤都具有相同的持续时间(比最慢的SQL语句长约100ms)。有没有办法从常规SQL计时中提取SQL计时,并将它们与我的“等待异步”步骤相关联?@SamSaffron-没关系,我想我已经找到了答案,请参见下文:)谢谢,但这与我上面的更新具有相同的效果,我将
MiniProfiler.Current
传递给任务:我得到了相同数量的负时间,SQL配置文件丢失。通过一些调整,我几乎可以工作-我在
任务
lambda内创建
newProfiler
,将
MiniProfiler.Current
传递给它,并在执行步骤后调用
AddProfilerResults
。它可以处理单个任务,但同时执行的多个任务似乎都以相同的时间完成。您是为每个
任务生成一个
newProfiler
,还是为每个
任务生成一个
?你能发布你写的新代码吗?是的,但这并不是我想要的分析器步骤——异步步骤和同步步骤混合在一起总是有点混乱。我希望SQL探查器计时出现在结果中,最好与等待结果的步骤相关联;可能不是线程安全的,请务必通读实现。此外,这可能会将数据插入一个随机点,也许您希望在运行查询之前存储头部。@SamSaffron-您说得很对,
AddSqlTiming
不是线程安全的。我已经将其移动到同步延续,并更新了上面的答案。但它仍然不太正确。@SamSaffron-Ah,不要介意SQL持续时间是正确的。我有3条并行SQL语句,每条语句耗时1到4秒。页面在5秒内完成,但分析器的
durationmillissecondsinsql
正确地保存了3条语句的总和(大约7秒)。所以“182.4%的sql”意味着我用并行任务节省了82.4%的时间。非常好。前几天我想到了并行SQL的想法,巧妙地使用它真的很有帮助。我正在考虑为Dapper so添加一个并行扩展。查询返回一个延迟的IEnumerable(如果结果没有准备好,它会暂停以从使用者端获取它们)@Kobi-我还没有看到,但我也有很多同步SQL计时(因此
MiniProfiler.Current.HasSqlTimings
对我来说总是正确的)。所有SQL调用都是异步的吗?如果是这样,这可能是一个bug,我不确定-该步骤应该有
hassqltiming
,因为它只检查
AddSqlTiming
添加到的相同的
List
。我认为这可能需要级联到
ParentTiming
,但可能
HasSqlTimings
应该级联下来。
// 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;
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;
                   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