【问题标题】:Best way to write an async method in C#在 C# 中编写异步方法的最佳方法
【发布时间】:2021-01-24 08:42:50
【问题描述】:

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

同步版本

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

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

异步版本 1

public Task<List<Contact>> GetContactsAsync()
{
    return Task.FromResult(this.dbContext.Contacts.ToList());
}

异步版本 2

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

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

    return ret;
}

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

这是相同的方法,但包含更多细节。如果您看到除了异步主题之外的问题,那么我很乐意在 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;
}

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

异步版本 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 ??;
}

异步版本 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;
}

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

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

【问题讨论】:

  • 您在这里有 2 个单独的问题,可能还有多个变体。无论如何,当你不是时不要假装是异步的。 blog.stephencleary.com/2013/11/… 如果有异步版本的方法使用它们,不要将它们包装在任务中(即使你没有展示,但只是把它放在那里)。
  • 也许,你还没有到提出问题的阶段,请查看 Stephen Clearys 的博客,他是关于任务、异步和等待方面阅读量最大的作者之一,并且经常在这里回答问题 ( 1000 个)
  • Should I expose asynchronous wrappers for synchronous methods? 剧透警告,答案是否定的。顺便说一句,您想通过使用异步方法增强服务的 API 来实现什么?您想提高服务的可扩展性吗?您想提高使用该服务的客户的响应能力吗?还有什么?
  • @TheodorZoulias 我猜这个想法是可能有一个实例可以调用服务方法但不会阻止后续命令的执行。这就是我对 async/await 的理解。但是,如果我已经正确理解了已引用的帖子,则该责任不取决于服务,而是取决于消费者选择使用该方法的方式(?)因此,我将添加到我的应用程序代码中...await Task.Run(()=&gt;ContactSvcInstance.GetContacts()) ...而不是??
  • 是的,没错。这就是专家的建议。您的服务不应该公开异步方法,除非它们是真正异步的,深入其核心。

标签: c# asynchronous .net-core async-await


【解决方案1】:

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

异步版本 1

这个问题是它实际上不是异步的。它只是做同样的同步工作并将其包装在 Task&lt;T&gt; 中。

异步版本 2

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

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

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

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

public List<Contact> GetContacts()
{
    return this.dbContext.Contacts.ToList(); 
}

一般来说,您希望从最低级别开始,即实际执行IQueryable的代码。在这种情况下,我假设这是 ToList,但如果 ToList 是您自己的方法,那么它可能是该方法调用的任何方法。找到最低级别的代码后,将 that 更改为使用 await,然后让 async 从那里增长。

【讨论】:

  • 我在发布后注意到了 ToListAsync() 方法,并且很好奇我是否应该使用它来创建“异步”版本而不是 Run() 或 FromResult() 方法。你在我问之前回答了这个问题!谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多