0

What is the best way to add non-Task element to existing Task<List<MyClass>>?

I have to add MyClass element nonTaskClass into result x that is Task List. The thing is that it is not in any way async. It is just some pure conditional logic that results into object. Something like this:

public Task<List<MyClass>> CheckAsync(Input input) {
    // Get some class based on sync logic/conditional logic
    var nonTaskClass = new MyClass();
    if (input.Form == 1 || input.Country = "Lithuania") {
        nonTaskClass.Message = "Some message goes here";
    } else {
        nonTaskClass.Message = "Another message";
    }
     
    // Create tasks (calls different methods/services and get data)
    // Task list actually is based on "nonTaskClass"

    // Get data from tasks and return
    var x = Task.WhenAll(tasks).ContinueWith(t =>
    {
        return t.Result.Select(o => new MyClass
        {
            Message = o.Message
        }).ToList();
    });
    // Need to add nonTaskClass into x, how?!!!!!

    return x;
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
renathy
  • 5,125
  • 20
  • 85
  • 149
  • 3
    Is there any reason you're not writing this as an async method? That would make things much simpler - you probably wouldn't need to use Task.WhenAll at all, as you could just await each task in turn (still potentially creating all the tasks first). – Jon Skeet Jan 17 '23 at 09:54
  • I will think about it. It was written by another developer. I have to add new requirements into existing code base. – renathy Jan 17 '23 at 09:57
  • 1
    See [Task.FromResult](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.fromresult?view=net-7.0) to create a task that just wraps a value. But I would probably just rewrite this to an async method. – JonasH Jan 17 '23 at 09:58
  • 1
    You wouldn't need to change the signature of the method, as far as calling code is concerned - making it an `async` method is purely an implementation detail. – Jon Skeet Jan 17 '23 at 10:00
  • If I add async how it solves my problem - to add "MyClass"? I would not have Tasks or what? – renathy Jan 17 '23 at 10:07
  • Why not add the class within the `ContinueWith` method? – Erik Philips Jan 17 '23 at 10:13
  • 1
    @JonSkeet the `Task.WhenAll` should not be omitted. For an explanation see [this question](https://stackoverflow.com/questions/18310996/why-should-i-prefer-single-await-task-whenall-over-multiple-awaits "Why should I prefer single 'await Task.WhenAll' over multiple awaits?"). – Theodor Zoulias Jan 17 '23 at 10:18
  • @TheodorZoulias: Potentially. It depends on what the OP wants to happen on failures. If the actual nature of the failures doesn't matter, it may well be better to fail *immediately* when the first one is noticed, rather than have to wait for *all* of them to be noticed. (Logging of the failures could be handled via ContinueWith, for example.) There are lots of options here... but I agree that there are potentially valid reasons for keeping Task.WhenAll. I just don't think we can say it definitely *should not* be omitted. – Jon Skeet Jan 17 '23 at 10:22
  • @JonSkeet awaiting each task after the other results in a behavior that is unlikely to be desirable in any scenario. If you want to propagate the first failure early, the "first" is likely to mean *chronologically*, not *positionally* in the list of tasks. Propagating the first failure chronologically [is not as simple](https://stackoverflow.com/questions/57313252/how-can-i-await-an-array-of-tasks-and-stop-waiting-on-first-exception) as awaiting one task at a time. – Theodor Zoulias Jan 17 '23 at 10:33
  • @TheodorZoulias: Yes, so you can use `Task.WhenAny` in that case (as per the answers in the linked post; I [blogged about this many years ago](https://codeblog.jonskeet.uk/2012/01/16/eduasync-part-19-ordering-by-completion-ahead-of-time/)). Basically there are a lot of options - not *just* Task`.WhenAll`. – Jon Skeet Jan 17 '23 at 11:23
  • @JonSkeet [firing-and-forgetting](https://stackoverflow.com/questions/61316504/proper-way-to-start-and-async-fire-and-forget-call/61320933#61320933) tasks is not something that should be recommended anyway. Ideally you'd want to cancel the running tasks when the first one fails, and then await all of them to complete. In case the tasks are not cancelable, then you are in a sad situation because you are probably working with bad APIs. AFAIK there is no API in the standard .NET libraries that propagates completion, after having initiated work that is still running in the background unattended. – Theodor Zoulias Jan 17 '23 at 11:37
  • @TheodorZoulias: There are plenty of things that are appropriate in applications that aren't appropriate in standard libraries (which don't have nearly as much context). But I'm going to stop debating this now, as it's not really appropriate for a Stack Overflow comment thread. – Jon Skeet Jan 17 '23 at 11:38
  • @JonSkeet agreed. In case you really insist about the merits of multiple awaits over the `Task.WhenAll`, you could post your arguments as answer in the [relevant question](https://stackoverflow.com/questions/18310996/why-should-i-prefer-single-await-task-whenall-over-multiple-awaits "Why should I prefer single 'await Task.WhenAll' over multiple awaits?"), and we could continue the discussion there. :-) – Theodor Zoulias Jan 17 '23 at 11:43

2 Answers2

3

I would make it async and change it to:

public async Task<List<MyClass>> CheckAsync(Input input)
{
        var nonTaskClass = new MyClass();
        if (input.Form == 1 || input.Country = "Lithuania")
        {
            nonTaskClass.Message = "Some message goes here";
        }
        else
        {
            nonTaskClass.Message = "Another message";
        }

        var tasks = GetSomeTasks();

        return (await Task.WhenAll(tasks))
                .Select(x => new MyClass { Message = x.Message })
                .Append(nonTaskClass)
                .ToList();
}
Magnus
  • 45,362
  • 8
  • 80
  • 118
3

If it wasn't feasible to refactor the method - including making it async - I would add Task.FromResult(nonTaskClass) to the list of tasks:

tasks.Add(Task.FromResult(nonTaskClass));

// Get data from tasks and return
var x = Task.WhenAll(tasks)...
tymtam
  • 31,798
  • 8
  • 86
  • 126