psaxton
psaxton

Reputation: 1843

Initialize local variables async

Using the async functionality available in .Net 4.5+, is there a way to initialize multiple local variables concurrently, without having to do it in two steps? is there another way to delay blocking the parent until an awaited variable is attempted to be dereferenced?

Example: I have a method in which I am given three IDs which are used to fetch objects from three separate services. Each service will take a non-trivial amount of time to return and I would like to minimize the clock time needed. There is no interdependency on the objects creation.

What I can do:

string MyFunc(long userId, long moduleId, long targetId) {
    var userTask = _userRepo.Get(userId);
    var moduleTask = _moduleRepo.Get(moduleId);
    var targetTask = _targetRepo.Get(targetId);

    var user = await userTask;
    var module = await moduleTask;

    var action = module.GetActionFor(user);

    var target = await targetTask;
    action.ApplyTo(target);

    return string.Format("{0} begins {1} at {2}",
        user.Name,
        action.Description,
        target.Location);
}

What can be done to remove the intermediary Tasks task variables?

Clarification:

I am looking for something more concise. I do not need the Tasks aside from the initial assignment and the results are used multiple times in the remainder of the operation.

Doing this results in essentially running the code synchronously. If each Get statement takes 1 second to return, the block takes 3 seconds to complete:

var user = await _userRepo.Get(userId);
var module = await _moduleRepo.Get(moduleId);
var target = await _targetRepo.Get(targetId);

Doing the following results in the code becoming hard to read, especially when the objects need to be used multiple times:

var user = _userRepo.Get(userId);
var module = _moduleRepo.Get(moduleId);
var target = _targetRepo.Get(targetId);

var action = (await module).GetActionFor(await user);
action.ApplyTo(await target);

var formattedString = string.Format("{0} begins {1} at {2}",
        (await user).Name,
        action.Description,
        (await target).Location);

Upvotes: 3

Views: 2026

Answers (2)

svick
svick

Reputation: 244988

I don't believe there is a good way to get exactly what you want (at least not without some serious compile-time meta-programming).

The closest I could figure out is:

User user = null;
Module module = null;
Target target = null;

await RunAll(
    async () => user = await _userRepo.Get(userId),
    async () => module = await _moduleRepo.Get(moduleId),
    async () => target = await _targetRepo.Get(targetId));

var action = module.GetActionFor(user);
action.ApplyTo(target);

var formattedString = string.Format("{0} begins {1} at {2}",
        user.Name,
        action.Description,
        target.Location);

Where RunAll() is defined like this:

static Task RunAll(params Func<Task>[] funcs)
{
    return Task.WhenAll(funcs.Select(f => f()));
}

Upvotes: 2

Matias Cicero
Matias Cicero

Reputation: 26321

If each Task instance has the same generic type parameter (for example, Task<object>), then you can do it all in one line:

object[] results = await Task.WhenAll<object>(_userRepo.Get(userId), 
                                              _moduleRepo.Get(moduleId), 
                                              _targetRepo.Get(targetId));

return string.Format("{0} begins {1} at {2}",
                     ((User)results [0]).Name,
                     ((Module)results [1]).Description,
                     ((Target)results [2]).Location);

According to MSDN - Task.WhenAll:

Creates a task that will complete when all of the supplied tasks have completed

Upvotes: 0

Related Questions