【问题标题】:How can it take 0 ticks to query the database asynchronously?怎么可能需要 0 tick 才能异步查询数据库?
【发布时间】:2017-03-13 08:52:14
【问题描述】:

我正在尝试使用IDbInterceptor 来尽可能准确地为实体框架的查询执行计时,实现Jonathan Allenanswer to a similar question 的变体:

public class PerformanceLogDbCommendInterceptor : IDbCommandInterceptor
{
    static readonly ConcurrentDictionary<DbCommand, DateTime> _startTimes =
            new ConcurrentDictionary<DbCommand, DateTime>();
    public void ReaderExecuted(DbCommand command,
            DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        Log(command, interceptionContext);
    }

    public void NonQueryExecuted(DbCommand command,
            DbCommandInterceptionContext<int> interceptionContext)
    {
        Log(command, interceptionContext);
    }

    public void ScalarExecuted(DbCommand command,
            DbCommandInterceptionContext<object> interceptionContext)
    {
        Log(command, interceptionContext);
    }

    private static void Log<T>(DbCommand command,
            DbCommandInterceptionContext<T> interceptionContext)
    {
        DateTime startTime;
        TimeSpan duration;

        if(!_startTimes.TryRemove(command, out startTime))
        {
            //Log exception
            return;
        }
        DateTime now = DateTime.UtcNow;
        duration = now - startTime;

        string requestGUID = Guid.Empty.ToString();
        var context = interceptionContext.DbContexts.SingleOrDefault();
        if (context == null)
        {
            //Log Exception
        }
        else
        {
            var businessContext = context as MyDb;
            if (businessContext == null)
            {
                //Log Exception
            }
            else
            {
                requestGUID = businessContext.RequestGUID.ToString();
            }
        }
        string message;

        var parameters = new StringBuilder();
        foreach (DbParameter param in command.Parameters)
        {
            parameters.AppendLine(param.ParameterName + " " + param.DbType
                + " = " + param.Value);
        }

        if (interceptionContext.Exception == null)
        {
            message = string.Format($"Database call took"
                + $" {duration.TotalMilliseconds.ToString("N3")} ms."
                + $" RequestGUID {requestGUID}"
                //+ $" \r\nCommand:\r\n{parameters.ToString() + command.CommandText}");
        }
        else
        {
            message = string.Format($"EF Database call failed after"
                + $" {duration.TotalMilliseconds.ToString("N3")} ms."
                + $" RequestGUID {requestGUID}"
                + $" \r\nCommand:\r\n{(parameters.ToString() + command.CommandText)}"
                + $"\r\nError:{interceptionContext.Exception} ");
        }
        if (duration == TimeSpan.Zero)
        {
            message += $" \r\nTime: start: {startTime.ToString("hh:mm:ss fffffff")}"
                + $" | now: {now.ToString("hh:mm:ss fffffff")}"
                + $" \r\n \r\nCommand:\r\n"
                + $"{parameters.ToString() + command.CommandText}";
        }
        System.Diagnostics.Debug.WriteLine(message);
    }


    public void NonQueryExecuting(DbCommand command,
            DbCommandInterceptionContext<int> interceptionContext)
    {
        OnStart(command);
    }

    public void ReaderExecuting(DbCommand command,
            DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        OnStart(command);
    }

    public void ScalarExecuting(DbCommand command,
            DbCommandInterceptionContext<object> interceptionContext)
    {
        OnStart(command);
    }
    private static void OnStart(DbCommand command)
    {
        _startTimes.TryAdd(command, DateTime.UtcNow);
    }
}

奇怪的是,每 10 个左右的查询执行一次需要 0 个滴答声。它似乎只在我异步运行它时发生,同时有几个查询。需要注意的另一件事是,当我再次查询相同的少数查询时,并不总是相同的查询需要 0 个滴答声。

此外,我目前正在测试的数据库位于本地网络上,而不是本地机器上 - 对它的 ping 时间是 0-1 毫秒 - 所以即使数据被缓存,我也看不到怎么可能需要 0 个滴答声。

顺便说一句,大多数查询花费的时间可疑地接近 1、2 和 3 毫秒(例如 0.997 毫秒到 1.003 毫秒)。对我来说,这听起来像是操作系统旋转线程 cpu-time 和/或 1ms 睡眠。我不介意这种情况发生,但我只想知道为什么,这样我就可以解释结果中的不准确之处。

这可能与ConcurrentDictionary 有关。但是当我现在进行测试时,我目前只调用一次异步(WCF)方法,等待每个异步数据库调用,所以据我了解,它甚至不应该一次启动更多调用。这是所谓的示例:

public async Task<IEnumerable<DTJobAPPOverview>> GetJobOverviewAsync()
    ...
    var efResponsibleUserFullName = await dbContext.tblUsers.Where(u =>
                u.UserID == efJob.ResponsibleUserID
            ).Select(u => u.FullName)
            .FirstOrDefaultAsync();
    dtJob.ResponsibleUserName = efResponsibleUserName;
    var efCase = await dbContext.tblCases.FirstOrDefaultAsync(c =>
            c.ID == efJob.FK_CaseID);
    dtJob.Case = Mapper.Map<DTCase>(efCase); //Automapper
    ...
}

顺便说一句,我知道我可能应该将整个应用程序转换为使用导航属性,但这是我们目前拥有的,所以请多多包涵。

【问题讨论】:

  • 不要减去DateTimes 来测量时间间隔——这不是它的用途。 Stopwatch 是正确的类,它为您提供亚毫秒级的精度,与 DateTime.UtcNow 不同。另外,您确定 DbCommand 具有适合您用例的身份吗?
  • windows 中的事件正在使用 Timer Tick 并已排队。当计时器滴答发生时,检查异步事件以查看它们是否发生,如果发生,则数据在进程之间传输。因此,您可以在同一个计时器滴答声中处理多个相同的事件。
  • @jdweng 我不确定你在说什么类型的事件,但是异步 I/O 完成肯定不会以任何方式与计时器滴答同步(尽管很明显,回调确实需要一个可能需要一些时间的线程),并且绝对没有一种机制可以检查每个计时器滴答所有异步事件以查看它们是否发生。您可以有数百万个待处理的异步操作,并且在它们真正收到信号之前不会占用一条 CPU 指令。
  • Luaan : 在哪里检查信号?它是检查注册信号状态的定时器事件。

标签: c# datetime asynchronous entity-framework-6 interceptor


【解决方案1】:

向您的网络管理员致敬 - 很少看到延迟如此之低的网络。

DateTime.UtcNow 的分辨率与系统计时器相同(不足为奇,因为系统计时器会更新当前时间:))。默认情况下,在 Windows NT 上,这是 10 毫秒 - 所以在干净的系统上,您只能获得 10 毫秒的精度。 10ms 的值可能意味着该操作根本不需要时间,或者需要 9.9ms,或者需要 19.9ms,这取决于您的运气。

在您的系统上,要么是某些应用程序更改了计时器频率(Chrome 和其他重动画的应用程序经常滥用),要么您正在运行 Windows 8+,它已移至无滴答计时器系统。无论如何,您的计时器精度为 1 毫秒 - 这就是您在日志中看到的。

如果您想要更高的精度/准确度,您需要使用StopwatchDateTime 无论如何都不是为您使用它而设计的,尽管只要您不太依赖它,它通常就足够好用了(DST/闰秒非常有趣:))。 Stopwatch 是。

最后,确保您的字典的键按照您假设的方式工作。你确定那些DbCommands 有你需要的那种身份吗? DbCommand 没有引用身份的合同要求,或者 EntityFramework 不重复使用 DbCommand 实例的合同要求。

【讨论】:

  • 转换为Stopwatch 肯定会给出更可信的结果。我完全忘记了那节课。我不是 100% 确定使用 DbCommand 来识别查询,但它似乎到目前为止工作。由于 EntityFramework 不保证它,我肯定看到依赖它的潜在问题。不幸的是,我没有任何好的替代方案。
  • 不管怎样,与Database.Log = m =&gt; System.Diagnostics.Debug.Write(m);相比,IDbInterceptor管道和/或网络中似乎增加了最多3毫秒的延迟,假设-- Completed in ## ms with result: SqlDataReader是返回的实际执行时间数据库。到目前为止,似乎唯一的选择是对默认日志消息进行子字符串化,我对此不太满意。再次感谢您的帮助。
  • 如何将代码实际转换为使用Stopwatch
  • 没关系,我找到了使用Stopwatch的实际EF6实现:github.com/aspnet/EntityFramework6/blob/master/src/…
猜你喜欢
  • 2013-04-05
  • 2017-03-29
  • 2020-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-02
  • 2019-02-26
相关资源
最近更新 更多