Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute all tasks simultaneously and await their completion?

I have a series of async methods that I would like to execute simultaneously. Each of these methods return true or false in regards to if they execute successfully or not. Their results are also logged in our audit trail so that we can diagnose issues.

Some of my functions are not dependent on all of these methods executing successfully, and we fully expect some of them to fail from time to time. If they do fail, the program will continue to execute and it will merely alert our support staff that they need to correct the issue.

I'm trying to figure out what would be the best method for all of these functions to execute simultaneously, and yet have the parent function await them only after they have all begun to execute. The parent function will return False if ANY of the functions fail, and this will alert my application to cease execution.

My idea was to do something similar to:

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;
}

Is there a better option? Should I restructure in any way? I'm simply trying to speed up execution time as I have a parent function that executes close to 20 other functions that are all independent of each other. Even at it's slowest, without async, it only takes about 1-2 seconds to execute. However, this will be scaled out to eventually have that parent call executed several hundred times (bulk insertions).

like image 779
JD Davis Avatar asked Oct 05 '15 15:10

JD Davis


People also ask

How do you wait for multiple tasks?

The correct way to await multiple tasks is the Task. WhenAll method: await Task. WhenAll(first, second); . Then you can await them individually to get their results, because you know that all have completed successfully.

Do you have to await Task run?

If it is some trivial operation that executes quickly, then you can just call it synchronously, without the need for await . But if it is a long-running operation, you may need to find a way to make it asynchronous.

Does parallel invoke wait for completion?

Now, the Parallel. Invoke does not wait for the methods to finish and the spinner is instantly off.

Can you await a task twice?

await hides all this complexity from you, and it allows you to await the same task in ten different places (very useful for e.g. asynchronous lazy initialization). can I be assured that the method pointed by task wont be executed twice even if the task is running or ran already ? @BilalFazlani Yes, you can.


Video Answer


1 Answers

Async methods have a synchronous part which is the part before the first await of an uncompleted task is reached (if there isn't one then the whole method runs synchronously). That part is executed synchronously using the calling thread.

If you want to run these methods concurrently without parallelizing these parts simply invoke the methods, gather the tasks and use Task.WhenAll to await for all of them at once. When all tasks completed you can check the individual results:

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;
}

If you do want to parallelize that synchronous part as well you need multiple threads so instead of simply invoking the async method, use Task.Run to offload to a ThreadPool thread:

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;
}

If all your methods return bool you can gather all the tasks in a list, get the results from Task.WhenAll and check whether all returned 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(_ => _);
}
like image 196
i3arnon Avatar answered Oct 09 '22 17:10

i3arnon