在 C# 中编写异步方法的最佳方法

Gar*_*rom 0 c# asynchronous async-await .net-core

我有一个服务对象,它只是对 Microsoft SQL 数据库之上的 EF Core 存储库执行查询。我正在尝试将这些同步方法转换为异步版本。这是一种简化方法的示例......

同步版本

public List<Contact> GetContacts()
{
    var ret = this.dbContext.Contacts.ToList(); 
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

我已经看到了几种不创建异步方法的方法......再说一遍......我已经看到了几种不同的方法来说明你应该如何创建异步方法。我陷入了以下两种方法中的哪一种被认为是创建异步方法的“正确”或“最佳”方式之间。

异步版本 1

public Task<List<Contact>> GetContactsAsync()
{
    return Task.FromResult(this.dbContext.Contacts.ToList());
}
Run Code Online (Sandbox Code Playgroud)

或者

异步版本 2

public async Task<List<Contact>> GetContactsAsync()
{
    List<Contact> ret = null;

    await Task.Run(()=>{
        ret = this.dbContext.Contacts.ToList(); 
    });

    return ret;
}
Run Code Online (Sandbox Code Playgroud)

如果查询包含多行逻辑,则可能会使情况进一步复杂化。

这是包含更多细节的相同方法。如果您看到异步主题之外的问题,那么我很乐意在 PM 或其他内容中听到它们,但是对于这个线程,我想专注于等待/异步的东西。

完整的同步代码

public PaginationResult<Contact> GetContacts(PaginationSpec pagingSpec, List<Expression<Func<Models.Contact, bool>>> filter = null)
{
    IQueryable<Contact> query = ContactService.GetContactQuery(this.ctx);

    if (filter != null && filter.Count > 0)
    {
        foreach (var item in filter)
        {
            query = query.Where(item);
        }
    }

    // get the record count
    //
    var recordcount = query.Count();

    // get the pagination
    //
    var stage = pagingSpec.ApplyPagination<Models.Contact>(query);

    // Construct the paged result.
    // 
    var ret = new PaginationResult<Models.Contact>(recordcount, pagingSpec.CurrentPage, pagingSpec.PageSize, stage.ToList());

    return ret;
}
Run Code Online (Sandbox Code Playgroud)

我将如何用适当的语法包装它以使此方法异步?我可以想到几种方法......

异步版本 1

public Task<PaginationResult<Contact>> GetContactsAsync(PaginationSpec pagingSpec, List<Expression<Func<Models.Contact, bool>>> filter = null)
{
    return Task.FromResult(GetContacts(pagingSpec, filter)) // simply call the synchronous method ??;
}
Run Code Online (Sandbox Code Playgroud)

或者

异步版本 2

public async Task<PaginationResult<Contact>> GetContactsAsync()
{
    PaginationResult<Contact> ret = null;

    await Task.Run(()=>{
    
        // Encapsulate the execution code within the RUN method ??
        //
        IQueryable<Contact> query = ContactService.GetContactQuery(this.ctx);

        if (filter != null && filter.Count > 0)
        {
            foreach (var item in filter)
            {
                query = query.Where(item);
            }
        }

        // get the record count
        //
        var recordcount = query.Count();

        // Apply the pagination spec to the query
        //
        var stage = pagingSpec.ApplyPagination<Models.Contact>(query);

        // Construct the esult.
        // 
        ret = new PaginationResult<Models.Contact>(recordcount, pagingSpec.CurrentPage, pagingSpec.PageSize, stage.ToList());
        });

    return ret;
}
Run Code Online (Sandbox Code Playgroud)

这些中的任何一个都不合适,比一个更好,还是我错过了第三个选项?

尝试学习这个等待/异步的东西,任何帮助表示赞赏!谢谢!

Ste*_*ary 7

这两者通常都被认为是不正确的。

异步版本 1

这个的问题在于它实际上不是异步的。它只是在做同样的同步工作并将它包装在一个Task<T>.

异步版本 2

这个问题是从调用者的角度来看它是异步的,但在它的实现中是同步的。它只是Task.Run用于在线程池线程上运行同步代码,因此它看起来是异步的。这就是我所说的“假异步”,这对于桌面应用程序来说是可以的(不理想),当然不推荐用于 ASP.NET 应用程序。

更合适的解决方案如下所示:

public async Task<List<Contact>> GetContactsAsync()
{
    return await this.dbContext.Contacts.ToListAsync();
}
Run Code Online (Sandbox Code Playgroud)

这是假设您有ToListAsync可用的方法。请注意与同步版本的相似之处:

public List<Contact> GetContacts()
{
    return this.dbContext.Contacts.ToList(); 
}
Run Code Online (Sandbox Code Playgroud)

一般来说,你要从底层开始,即实际执行IQueryable. 在这种情况下,我假设那是ToList,但如果ToList是您自己的方法,那么它可能是该方法调用的任何内容。一旦你找到了最低级别的代码,然后更改在使用await,并让异步从那里成长。