【问题标题】:FirstOrDefaultAsync vs FirstOrDefaultFirstOrDefaultAsync 与 FirstOrDefault
【发布时间】:2020-10-26 17:39:10
【问题描述】:

我正在为我的 ASP.NET Core WebApi 项目使用 Entity Framework Core。
我在使用 Linq 时遇到了这两种方法,我很想知道 FirstOrDefaultAsyncFirstOrDefault 之间的区别?

【问题讨论】:

标签: c# entity-framework entity-framework-core


【解决方案1】:

这里是优秀的媒体帖子:https://medium.com/@deep_blue_day/long-story-short-async-await-best-practices-in-net-1f39d7d84050

总结:

基本上有两种情况下 Async/Await 是正确的解决方案。

I/O-bound 工作:您的代码将等待某些事情,例如来自数据库的数据、读取文件、调用 Web 服务。在这种情况下,您应该使用 Async/Await,但不要使用任务并行库。

CPU 密集型工作:您的代码将执行复杂的计算。在这种情况下,您应该使用 Async/Await,但使用 Task.Run 在另一个线程上生成工作。您也可以考虑使用任务并行库。

您的案例属于 I/O 密集型工作。

那为什么会有非异步版本呢?那是为了支持遗留代码和/或由于执行重构而不允许使用异步方法的代码。 (一种异步方法,意味着所有调用堆栈都应该是异步的)。

【讨论】:

    【解决方案2】:

    让我们从这些方法的实现开始:

    FirstOrDefault

    Source(它有两个没有谓词的重载)

    public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source)
    {
        if (source == null)
            throw Error.ArgumentNull("source");
        return source.Provider.Execute<TSource>(
            Expression.Call(
                null,
                GetMethodInfo(Queryable.FirstOrDefault, source),
                new Expression[] { source.Expression }
            ));
    }
    
    public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        if (source == null)
            throw Error.ArgumentNull("source");
        if (predicate == null)
            throw Error.ArgumentNull("predicate");
        return source.Provider.Execute<TSource>(
            Expression.Call(
                null,
                GetMethodInfo(Queryable.FirstOrDefault, source, predicate),
                new Expression[] { source.Expression, Expression.Quote(predicate) }
            ));
    }
    

    FirstOrDefaultAsync

    Source(它有两个没有谓词的重载)

    public static Task<TSource> FirstOrDefaultAsync<TSource>(
        [NotNull] this IQueryable<TSource> source,
        CancellationToken cancellationToken = default)
    {
        Check.NotNull(source, nameof(source));
    
        return ExecuteAsync<TSource, Task<TSource>>(QueryableMethods.FirstOrDefaultWithoutPredicate, source, cancellationToken);
    }
    
    public static Task<TSource> FirstOrDefaultAsync<TSource>(
        [NotNull] this IQueryable<TSource> source,
        [NotNull] Expression<Func<TSource, bool>> predicate,
        CancellationToken cancellationToken = default)
    {
        Check.NotNull(source, nameof(source));
        Check.NotNull(predicate, nameof(predicate));
    
        return ExecuteAsync<TSource, Task<TSource>>(QueryableMethods.FirstOrDefaultWithPredicate, source, predicate, cancellationToken);
    }
    

    他们都打电话给following ExecuteAsync overload

    private static TResult ExecuteAsync<TSource, TResult>(
        MethodInfo operatorMethodInfo,
        IQueryable<TSource> source,
        Expression expression,
        CancellationToken cancellationToken = default)
    {
        if (source.Provider is IAsyncQueryProvider provider)
        {
            if (operatorMethodInfo.IsGenericMethod)
            {
                operatorMethodInfo
                    = operatorMethodInfo.GetGenericArguments().Length == 2
                        ? operatorMethodInfo.MakeGenericMethod(typeof(TSource), typeof(TResult).GetGenericArguments().Single())
                        : operatorMethodInfo.MakeGenericMethod(typeof(TSource));
            }
    
            return provider.ExecuteAsync<TResult>(
                Expression.Call(
                    instance: null,
                    method: operatorMethodInfo,
                    arguments: expression == null
                        ? new[] { source.Expression }
                        : new[] { source.Expression, expression }),
                cancellationToken);
        }
    
        throw new InvalidOperationException(CoreStrings.IQueryableProviderNotAsync);
    }
    

    Provider 通话比较

    同步

    return source.Provider.Execute<TSource>(Expression.Call(...))
    

    异步

    return provider.ExecuteAsync<TResult>(Expression.Call(...))
    

    如您所见,这两个调用之间的区别在于您与数据源的通信方式:

    • 如果您以同步方式执行此操作,则在底层网络驱动程序执行请求的 I/O 操作时,您的调用线程将被阻塞并保持空闲。
    • 如果您以异步方式执行此操作,则在将工作分派给底层网络驱动程序以非阻塞方式执行请求的 I/O 操作后,您的调用线程将被释放。因此,您的调用者线程可以在等待驱动程序通知调度程序请求的操作已运行完成时执行其他代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-12
      • 2011-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多