【问题标题】:Why does discarding an EntityFramework.FirstOrDefaultAsync() query or even a OracleCommand.ExecuteNonQueryAsync behave just like I awaited it?为什么丢弃 EntityFramework.FirstOrDefaultAsync() 查询甚至 OracleCommand.ExecuteNonQueryAsync 的行为就像我等待它一样?
【发布时间】:2022-11-17 17:45:47
【问题描述】:

因为它真的像我期望的那样变成了火而忘记了。我需要在 Task.Run(() => DbCallTestCommand()) 中调用它,这对我来说似乎有点不必要?

这是在 LINQPad 中重现问题的示例代码:

void Main()
{
    "Main start!".Dump();

    _ = DbCallTestCommand();

    "Main Done!".Dump();
}

//Makes no sense to really execute this as a fire and forget but I included it because Im guessing that EF also uses DbCommand behind the scenes.

async Task DbCallTestEF()
{
    var db = new EventContext(this.connectString);

    var query = db.TBLEVENT.Where(t => t.STARTDATE > DateTime.Now.AddDays(-65));

    var result = await query.ToListAsync(); //Takes about 10 seconds to run in total

    "DbCallTestEF done".Dump();
}

async Task DbCallTestCommand()
{
    using var connection = new OracleConnection(this.connectString);
    await connection.OpenAsync();

    using var command = connection.CreateCommand();
    command.CommandText = "UPDATE_STATISTICS"; //<- long running statistic calculation job that the client shouldn't need to wait for.
    command.CommandType = CommandType.StoredProcedure;

    await command.ExecuteNonQueryAsync();

    "DbCallTestCommand done".Dump();
}

结果:
主线开始!
DbCallTest 命令!
主要完成!

这里的预期结果(恕我直言)是主要方法应该在丢弃的方法之前完成。因为我没有在 DbCallTestCommand() 上使用 await,但这不是这里发生的情况。

但是,如果我改为丢弃一个只等待 Task.Delay 的方法。方法。然后它按预期工作。 Main 方法在丢弃的方法之前完成。

看这里:

void Main()
{
    "Main start!".Dump();

    _ = TaskDelayTest();

    "Main Done!".Dump();
}

async Task TaskDelayTest()
{
    await Task.Delay(10000);
    
    "TaskDelayTest done!".Dump();
}

结果:(这是丢弃任务的预期结果):
主线开始!
主要完成!
任务延迟测试完成!

我对此感到很困惑,我真的认为两个丢弃应该表现相同(即不要等待方法完成后再继续)。所以我想知道是否有人知道这样做的原因,这是否确实是正确的行为?

【问题讨论】:

  • 看起来这两个链接是一样的。有些方法只能异步完成。使用 FirstOrDefault 将在仅返回一个结果时更快地终止查询,而不是等到找到所有结果。
  • 我使用图像是因为我也想展示我得到的结果。但我会改变它!
  • 不要使用await Task.Delay(1);。相反,删除 async 关键字并返回 Task.FromResult() 或对于没有返回类型的方法,Task.CompletedTask
  • @EricJ 我已经更改了上面的代码以尽量避免混淆。但是 main 方法是否异步并不重要。如果您尝试丢弃等待 dbCommand 异步方法的异步方法(例如 await command.ExecuteNonQueryAsync),丢弃仍然不会按预期运行。

标签: c# asynchronous .net-6.0


【解决方案1】:

丢弃仍然没有按预期运行。

它的行为应该如此。具体来说,它根本不影响程序流程。您可以删除丢弃分配并观察相同的行为。

Main() 函数继续执行,而调用 DbCallTestCommand() 时创建的 Task 继续执行。

这是丢弃任务的预期结果

您不会丢弃具有该语法的任务。您告诉编译器您不关心跟踪从变量中的函数调用(在本例中为任务)返回的任何内容。任务仍然被创建并继续执行直到完成。

通过以下修改(包括 LinqPad 的 .Dump()),您可以看到不依赖于您的数据库:

// The behavior is the same if you change this to
// void Main()
Task Main()
{
    "Main start!".Dump();

    // Same as:
    // DbCallTestCommand();
    _ = DbCallTestCommand();

    "Main Done!".Dump();

    // To force the program to exit immediately, uncomment this:
    // Environment.Exit(0);
    return Task.CompletedTask;
}

async Task DbCallTestCommand()
{
    "DbCallTestCommand() starting".Dump();
    for (int i = 0; i < 3; i++)
    {
        $"DbCallTestCommand() {i}".Dump();
        await Task.Delay(1000);
    }
    
    "DbCallTestCommand() ending".Dump();
}

输出:

重头开始!

DbCallTestCommand() 开始

DbCallTestCommand() 0

主要完成!

DbCallTestCommand() 1

DbCallTestCommand() 2

DbCallTestCommand() 结束

您还将收到编译器警告 BC42358,通知您正在发生的事情:

因为没有等待这个调用,所以当前方法的执行 在呼叫完成之前继续

【讨论】:

  • 是的,当 DbCallTestCommand 仅等待 Task.Delay 时,这就是发生的情况,也是我期望发生的情况。但是,如果您在 DbCallTestCommand 内部等待 oracleCommand.ExecuteNonQueryAsync,那么“_ = DbCallTestCommand”突然表现得就像我在等待它一样。即程序等待 _ = DbCallTestCommand 在继续之前完成,这只有在我写 await DbCallTestCommand() 时才会发生
  • 我不明白这怎么可能发生,因为在单独的任务中运行该代码的行为是一个 .NET 问题,与 DbCallTestCommand() 内部发生的事情无关。
  • 是的,这很奇怪。但无论如何,这就是目前正在发生的事情。这就是为什么我觉得是时候在这里问一个关于 stackoverflow 的问题了。因为这没有任何意义。
【解决方案2】:

在看到关于即将发布的 ODP.NET 7 版本的推文的评论后。他们在哪里询问此版本是否最终会带有“真正的异步”。我开始做一些挖掘。

事实证明。 OracleCommand 中没有异步方法是真正异步的。它们显然是假的。我猜这就是为什么使用 _ 丢弃运算符的“即发即忘”根本不适用于 OracleCommand 执行的最可能原因。

然而,真正的异步将添加到另一个 ODP.NET 发布版本中,该版本将于 2023 年某个时候发布。有关详细信息,请参阅:https://github.com/oracle/dotnet-db-samples/issues/144

【讨论】:

    【解决方案3】:

    因为 Oracle 驱动程序实际上并不是“真正的”异步。查看更多here

    如果出于某种原因您需要即发即弃功能作为快速修复,您可以将调用包装到Task.Run

    另请注意,您的示例代码根本不能保证等待即发即弃任务的完成,因此根据您如何运行它,“真正的”异步版本可能会错过 DbCallTestCommand 在第一次之后的任何输出等待。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-15
      • 2021-10-31
      • 2021-11-30
      • 1970-01-01
      • 2017-05-07
      • 1970-01-01
      • 2015-10-18
      相关资源
      最近更新 更多