3

Based off this question I'm trying to set up code to save several images to Azure Blob Storage in parallel. This method below works fine and awaiting Task.WhenAll(tasks) awaits for all to complete before continuing.

The only trouble is, I would like to be able to find out if each request to store the information in our database actually succeeded. _db.AddImageAsync returns a bool and the code below waits for all tasks to complete but when I check the result of all the tasks each is false (even if I actually returned true inside the brackets).

Each task in the Enumerable says the result has not yet been computed even though I stepped through with breakpoints and each has been carried out.

  var tasks = wantedSizes.Select(async (wantedSize, index) =>
  {
    var resize = size.CalculateResize(wantedSize.GetMaxSize());
    var quality = wantedSize.GetQuality();

    using (var output = ImageProcessHelper.Process(streams[index], resize, quality))
    {
        var path = await AzureBlobHelper.SaveFileAsync(output, FileType.Image);
        var result = await _db.AddImageAsync(id, wantedSize, imageNumber, path);
        return result;
    }
  });

  await Task.WhenAll(tasks)

  if (!tasks.All(task => task.Result))
      return new ApiResponse(ResponseStatus.Fail);

Any help is much appreciated!

Community
  • 1
  • 1
James Mundy
  • 4,180
  • 6
  • 35
  • 58
  • 3
    You appear to be using `_db` in multiple threads at the same time, are you allowed to have multiple threads call `_db.AddImageAsync` at the same time? (This is not related to your question at all but it is a common error I see a lot of people make with async) – Scott Chamberlain Nov 20 '15 at 15:30
  • Good point, @ScottChamberlain - I suspect not. I like many before me have been caught up - it hasn't added anything to the database! Out of interest, do you know what exactly happens behind the scenes when you try and use a method that isn't thread safe like this? – James Mundy Nov 20 '15 at 16:55
  • Using a DbContext for multiple concurrent requests (even if the requests are all made on the same thread) is not supported and has undefined behavior, so you will get [Nasal Demons](http://www.catb.org/jargon/html/N/nasal-demons.html) (aka: Anything could happen, it could work, it could throw a error, it could look like it is working but put corrupt data in your database, it could make daemons fly out of your nose) – Scott Chamberlain Nov 20 '15 at 16:56

1 Answers1

7

Because .Select( is lazy evaluated and returns a IEnumerable<Task<bool>> you are causing the .Select( to be run multiple times when you iterate over the result multiple times. Throw a .ToList() on it to make it a List<Task<bool>> and that will only execute the .Select( once and the multiple enumerations will be over the returned List<Task<bool>> which will not have side effects.

  var tasks = wantedSizes.Select(async (wantedSize, index) =>
  {
    var resize = size.CalculateResize(wantedSize.GetMaxSize());
    var quality = wantedSize.GetQuality();

    using (var output = ImageProcessHelper.Process(streams[index], resize, quality))
    {
        var path = await AzureBlobHelper.SaveFileAsync(output, FileType.Image);
        //Double check your documentation, is _db.AddImageAsync thread safe?
        var result = await _db.AddImageAsync(id, wantedSize, imageNumber, path);
        return result;
    }
  }).ToList(); //We run the Select once here to process the .ToList().

  await Task.WhenAll(tasks) //This is the first enumeration of the variable "tasks".

  if (!tasks.All(task => task.Result)) //This is a 2nd enumeration of the variable.
      return new ApiResponse(ResponseStatus.Fail);
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 2
    +1, and to clarify: the reason why everything is returning false is that `AddImageAsync` presumably returns `false` the second time it's called with the same ID, since nothing is changed about the image the second time around. – StriplingWarrior Nov 20 '15 at 15:51