同时执行所有任务并等待它们完成?

5
我有一系列异步方法,希望同时执行。每个方法都会返回执行成功或失败的true或false,并在审计跟踪中记录结果,以便我们诊断问题。
有些函数并不依赖于所有这些方法的成功执行,并且我们完全预期其中一些会时不时地失败。如果它们失败了,程序将继续执行,并仅向我们的支持人员发出警报,告知需要纠正该问题。
我试图找出最佳方法来让所有这些函数同时执行,但只有在它们全部开始执行后,父函数才等待它们。如果任何一个函数失败,父函数将返回False,并提示我的应用程序停止执行。
我的想法是做类似于下面的操作:
public async Task<bool> SetupAccessControl(int objectTypeId, int objectId, int? organizationId)
{
    using (var context = new SupportContext(CustomerId))
    {
        if (organizationId == null)
        {
            var defaultOrganization = context.Organizations.FirstOrDefault(n => n.Default);
            if (defaultOrganization != null)  organizationId = defaultOrganization.Id;
        }
    }

    var acLink = AcLinkObjectToOrganiation(organizationId??0,objectTypeId,objectId);

    var eAdmin = GrantRoleAccessToObject("eAdmin", objectTypeId, objectId);
    var defaultRole = GrantRoleAccessToObject("Default", objectTypeId, objectId);

    await acLink;
    await eAdmin;
    await defaultRole;

    var userAccess = (objectTypeId != (int)ObjectType.User) || await SetupUserAccessControl(objectId);

    return acLink.Result && eAdmin.Result && defaultRole.Result && userAccess;
}

public async Task<bool> SetupUserAccessControl(int objectId)
{
    var everyoneRole = AddToUserRoleBridge("Everyone", objectId);
    var defaultRole = AddToUserRoleBridge("Default", objectId);

    await everyoneRole;
    await defaultRole;

    return everyoneRole.Result && defaultRole.Result;
}

有更好的选择吗?我应该做出任何改变吗?我只是试图加快执行时间,因为我有一个父函数,它执行接近20个互不相关的函数。即使没有异步,在最慢的情况下,执行时间也只需要大约1-2秒。但是,这将被扩展到最终多次调用该父函数(批量插入)。


我曾经用Parallel.Invoke来处理这些东西。 - Moby Disk
3
你可能需要查看 Task.WhenAll。就结果值而言,如果不知道涉及的类型的一半,很难确定。 - Jon Skeet
幸运的是,如果我不需要将值传递给父级(或等待它们在另一个函数中使用),所有函数都将返回一个真或假值。 - JD Davis
1个回答

6

异步方法包含同步部分,在未完成任务的第一个等待之前执行(如果没有等待,则整个方法运行同步)。该部分使用调用线程同步执行。

如果您想同时运行这些方法而不并行化这些部分,只需调用这些方法,收集任务并使用Task.WhenAll一次等待所有任务。当所有任务都完成时,您可以检查各个结果:

async Task<bool> SetupUserAccessControlAsync(int objectId)
{
    var everyoneRoleTask = AddToUserRoleBridgeAsync("Everyone", objectId);
    var defaultRoleTask = AddToUserRoleBridgeAsync("Default", objectId);

    await Task.WhenAll(everyoneRoleTask, defaultRoleTask)

    return await everyoneRoleTask && await defaultRoleTask;
}

如果你想并行处理同步部分,你需要使用多个线程。因此,不仅要调用异步方法,还需要使用Task.Run将其转移到ThreadPool线程中:

async Task<bool> SetupUserAccessControlAsync(int objectId)
{
    var everyoneRoleTask = Task.Run(() => AddToUserRoleBridgeAsync("Everyone", objectId));
    var defaultRoleTask = Task.Run(() => AddToUserRoleBridgeAsync("Default", objectId));

    await Task.WhenAll(everyoneRoleTask, defaultRoleTask)

    return await everyoneRoleTask && await defaultRoleTask;
}

如果您的所有方法都返回bool,您可以将所有任务收集到列表中,从Task.WhenAll获取结果,并检查是否全部返回了true:

async Task<bool> SetupUserAccessControlAsync(int objectId)
{
    var tasks = new List<Task<bool>>();
    tasks.Add(AddToUserRoleBridgeAsync("Everyone", objectId));
    tasks.Add(AddToUserRoleBridgeAsync("Default", objectId));

    return (await Task.WhenAll(tasks)).All(_ => _);
}

1
我并不担心它们完全并发执行,我只是想确保每个调用尽可能快地执行,而不会阻塞下一个调用的执行。 - JD Davis
1
@Jdsfighter 这些句子相互矛盾。如果您希望第二个操作尽快开始,则需要第一个操作尽快释放调用线程。这意味着将同步部分甚至转移到不同的线程中。 - i3arnon
每个调用都执行一系列数据库函数。我只想确保我可以快速执行这些函数,而不会像同步数据库调用那样阻塞调用线程很长时间。 - JD Davis
@Jdsfighter 如果你不使用 Task.Run,调用线程将按顺序运行这些同步部分。如果这是一个较长的时间取决于你的代码。Task.Run 将这些部分转移到一个 ThreadPool 线程上,因此调用线程尽可能少做工作。 - i3arnon
最终,我终于站起来去找我们的一位资深开发人员交流,他帮助我更好地理解了这个问题。看起来我经常在异步和并行方面混淆。他还提到,除非操作相当耗费 CPU,否则我应该避免将操作放在单独的线程中。 - JD Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接