0

This is an offshoot of Awaiting multiple Tasks with different results.

Is this (setting result variables INSIDE the task):

var cat;
var house;
var car;

var catTask   = Task.Run(() => { cat   = FeedCat();   } );
var houseTask = Task.Run(() => { house = SellHouse(); } );
var carTask   = Task.Run(() => { car   = BuyCar();    } );

await Task.WhenAll(catTask, houseTask, carTask);

considered ill-advised or un-idomatic versus this (setting result variables OUTSIDE the task)?:

var catTask   = FeedCat();
var houseTask = SellHouse();
var carTask   = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat   = await catTask;
var house = await houseTask;
var car   = await carTask;

Edit 1: For the sake of simplicity, just assume FeedCat(), SellHouse(), and BuyCar() return strings. The thrust of the question is where variable assignment is taking place.

Dai
  • 141,631
  • 28
  • 261
  • 374
amonroejj
  • 573
  • 4
  • 16
  • 2
    Without knowing what `FeedCat` etc do, it's really hard to know what's going on here. A [mcve] would make it easier to talk about *very specific* differences. – Jon Skeet Aug 09 '23 at 15:49
  • 2
    I renamed this question because you're really asking about closures, not Tasks. – Dai Aug 09 '23 at 15:56
  • 2
    Is `FeedCat()` an `async` method - or a normal (synchronous, not `async`) method? If the former then it should be named `FeedCatAsync()` and you (probably) don't need to use `Task.Run`. – Dai Aug 09 '23 at 15:57
  • @Dai I would guess OP is considering different implementations for methods, sync and async. – Guru Stron Aug 09 '23 at 15:59
  • Don't get too into the weeds. Assume that the fictional functions are parallel-worthy and doing/returning the right things. – amonroejj Aug 09 '23 at 16:47
  • As a general rule, asynchronous code encourages functional coding patterns. The pattern of returning results rather than setting variables as side effects is more functional, so IMO it's nore natural here. – Stephen Cleary Aug 09 '23 at 17:20

1 Answers1

2

The semantic difference is definite assignment and typing; in the first version, you can't just use var cat - it won't have a type and the compiler will never know that it gets assigned. From the compiler's perspective, the lambda isn't known to have ever run, so definite assignment can't be used to prove anything useful.

The implementation difference will be that this forces cat to be "captured", which increases allocations, although in many cases you may not notice.

There's also a difference in terms of code readability; it is usually easier to understand (and test in isolation) something that returns a value, over something that updates ambient state.

Finally, note that technically the second version could run entirely non-asynchronously and sequentially, if none of the operations await anything (or return an incomplete task somehow else). The first version will get more parallel in those scenarios, but via more complexity.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900