在 c# 中何时需要异步和等待

本文关键字:异步 等待 何时需 | 更新日期: 2023-09-27 18:31:13

正如标题所说。 我想知道我是否正在异步编写并在不需要时等待。

我见过这样的带有异步标签的方法

public async Task CreateAsync(User user)
{
    if (_context.Entry<User>(user).State == EntityState.Detached)
    {
        _context.Set<User>().Add(user);
    }
    _context.Entry<User>(user).State = EntityState.Added;
    await _context.SaveChangesAsync();
}

就像这样没有它

public Task CreateAsync(User user)
{
    if (_context.Entry<User>(user).State == EntityState.Detached)
    {
        _context.Set<User>().Add(user);
    }
    _context.Entry<User>(user).State = EntityState.Added;
    return _context.SaveChangesAsync();
}

两者都编译良好。 我总是添加异步和等待关键字,想知道我是否做错了并在不需要它们时编写它们?

编辑:

如果您实际上返回了一个值,则应使用 async/await 关键字编写该值,还是不使用。这是一个带有关键字的版本

public async Task<User> CreateAsync(User user)
{
    if (_context.Entry<User>(user).State == EntityState.Detached)
    {
        _context.Set<User>().Add(user);
    }
    _context.Entry<User>(user).State = EntityState.Added;
    await _context.SaveChangesAsync();
    return user;
}

这是另一个例子

public Task<User> FindByIdAsync(long userId)
{
    return _context.Users.FindAsync(userId);
}
public async Task<User> FindByIdAsync(long userId)
{
    return await _context.Users.FindAsync(userId);
}

编辑 2

到目前为止,答案很好,但最后一个例子是 1 个。 由于我有异步调用,我将如何应对从 1 个方法调用多个异步函数。 这是我拥有的一个例子,但我不知道它是否正确。 我不希望该方法在所有 AddAsync 方法完成之前退出。这是正确的吗

private async Task AddPermissions(DataContext context)
{
    var permissionService = new PermissionService(context);
    await permissionService.AddAsync(new Permission("CanView", "View company"));
    await permissionService.AddAsync(new Permission("CanAdd", "Add and view company"));
    await permissionService.AddAsync(new Permission("CanEdit", "Edit and view company"));
    await permissionService.AddAsync(new Permission("CanDelete", "Delete and view company record"));
    await permissionService.AddAsync(new Permission("CanAdd", "Add new pages"));
    await permissionService.AddAsync(new Permission("CanEdite", "Edit existing pages"));
    await permissionService.AddAsync(new Permission("CanDelete", "Delete a page"));
    await permissionService.AddAsync(new Permission("CanAdd", "Add new page content"));
    await permissionService.AddAsync(new Permission("CanEdit", "Edit existing page content"));
    await permissionService.AddAsync(new Permission("CanDelete", "Delete page content"));
}

在 c# 中何时需要异步和等待

我直言,如果您希望SaveChangesAsync操作在方法返回时完成,或者您需要对异步操作的结果执行某些操作,则只需要使用该await。在这种情况下,您不会对它执行任何操作,因此最好不要使用异步方法,并避免使用该方法生成状态机,从而生成更高效的代码。

关于你的第二次编辑,你是对的。尽管该方法将在遇到第一个await后立即返回,但所有其他等待的语句将在线程池上一个接一个地执行,然后更新任务的结果。因此,如果您await AddPermissions该语句将仅在所有内部permissionService.AddAsync调用完成后完成,除非引发异常。

如有必要,还可以并行执行permissionService.AddAsync调用,方法是将返回的任务存储在列表中,然后等待Task.WhenAll

private async Task AddPermissions(DataContext context)
{
    var permissionService = new PermissionService(context);
    List<Task> permissionRequests = new List<Task>();
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanView", "View company")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanAdd", "Add and view company")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanEdit", "Edit and view company")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanDelete", "Delete and view company record")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanAdd", "Add new pages")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanEdite", "Edit existing pages")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanDelete", "Delete a page")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanAdd", "Add new page content")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanEdit", "Edit existing page content")));
    permissionRequests.Add(permissionService.AddAsync(new Permission("CanDelete", "Delete page content")));
    await Task.WhenAll(permissionRequests);
}

因此,每次调用permissionService.AddAsync都会启动请求并将相应的任务添加到列表中。启动所有请求后,您可以使用 await Task.WhenAll await完成所有请求,这将等到它们全部完成或返回错误。引发的任何异常都将存储在从 Task.WhenAll 返回的任务中。等待该任务将重新引发第一个异常,但您可以使用包含 Task.Excpetion 属性访问所有这些异常,该属性包含一个 AggregatedException,这反过来又包含所有引发的异常。

第二个版本效率稍高,但它不允许你稍后返回并添加更多代码,而无需重写它以使用 async/await。

差不多就是这样。如果我在我的代码库中看到任何一种模式,我都不会抱怨。

这个话题可能很深入,但这里有一个高层次的概述。

您上面发布的两个版本的代码之间存在关键区别。但首先是重要的相似性

它们都告诉_context异步保存更改。因为这就是SaveChangesAsync的实施方式。

<小时 />

现在,区别..

在第一个版本中,当您使用 asyncawait 关键字时,任何可能在await调用之后的代码(在这种情况下,await调用是函数中的最后一次调用),编译器会将其转换为延续,并且应该在await调用完成后执行(异步)。这包括可能包装异步调用的任何异常处理。

相反,在第二个版本中,当我们把异步调用的返回值作为Task时,当异步操作已经启动并且尚未完成时,执行将在该方法中继续(它不会被编译器变成延续)。没有代码设置为在异步操作完成后执行(除非在Task对象上显式使用 .ContinueWith)。

<小时 />

为什么要使用一个而不是另一个?

同样,在高级别asyncawait对于正常情况应该很好,在这些情况下,您希望利用编译器为您做一些魔术的优势,以便更轻松地处理异步调用。但是,对于某些方案,它的灵活性也较低。示例 - 如果要异步启动 10 个 ping 操作,然后在所有 10 个操作完成后写入延续,该怎么办?对于每个异步 ping 使用 asyncawait 关键字是不可能的。(第一次调用 await 会使代码的其余部分成为第一次异步调用的延续)。

<小时 />

在互联网上搜索更多有关async await的详细信息..它可能非常深入,但值得了解细节。

更简单的

方法是调用 Task.Factory.StartNew

Task.Factory.StartNew(() => new Permission("CanView", "View company"));
Task.Factory.StartNew(() => new Permission("CanAdd", "Add and view company"));
...