1

I have a list of objects that I need to run a long running process on and I would like to kick them off asynchronously, then when they are all finished return them as a list to the calling method. I've been trying different methods that I have found, however it appears that the processes are still running synchronously in the order that they are in the list. So I am sure that I am missing something in the process of how to execute a list of tasks. Here is my code:

public async Task<List<ShipmentOverview>> GetShipmentByStatus(ShipmentFilterModel filter)
    {
        if (string.IsNullOrEmpty(filter.Status))
        {
            throw new InvalidShipmentStatusException(filter.Status);
        }

        var lookups = GetLookups(false, Brownells.ConsolidatedShipping.Constants.ShipmentStatusType);

        var lookup = lookups.SingleOrDefault(sd => sd.Name.ToLower() == filter.Status.ToLower());

        if (lookup != null)
        {
            filter.StatusId = lookup.Id;
            var shipments = Shipments.GetShipments(filter);

            var tasks = shipments.Select(async model => await GetOverview(model)).ToList();

            ShipmentOverview[] finishedTask = await Task.WhenAll(tasks);

            return finishedTask.ToList();

        }
        else
        {

            throw new InvalidShipmentStatusException(filter.Status);

        }
    }


        private async Task<ShipmentOverview> GetOverview(ShipmentModel model)
    {
        String version;
        var user = AuthContext.GetUserSecurityModel(Identity.Token, out version) as UserSecurityModel;

        var profile = AuthContext.GetProfileSecurityModel(user.Profiles.First());

        var overview = new ShipmentOverview
        {
            Id = model.Id,
            CanView = true,
            CanClose = profile.HasFeatureAction("Shipments", "Close", "POST"),
            CanClear = profile.HasFeatureAction("Shipments", "Clear", "POST"),
            CanEdit = profile.HasFeatureAction("Shipments", "Get", "PUT"),
            ShipmentNumber = model.ShipmentNumber.ToString(),
            ShipmentName = model.Name,
        };

        var parcels = Shipments.GetParcelsInShipment(model.Id);

        overview.NumberParcels = parcels.Count;

        var orders = parcels.Select(s => WareHouseClient.GetOrderNumberFromParcelId(s.ParcelNumber)).ToList();

        overview.NumberOrders = orders.Distinct().Count();



        //check validations
        var vals = Shipments.GetShipmentValidations(model.Id);

        if (model.ValidationTypeId == Constants.OrderValidationType)
        {
            if (vals.Count > 0)
            {
                overview.NumberOrdersTotal = vals.Count();

                overview.NumberParcelsTotal = vals.Sum(s => WareHouseClient.GetParcelsPerOrder(s.ValidateReference));
            }
        }


        return overview;
    }
Adam
  • 264
  • 8
  • 22
  • 2
    It would really help if you posted a [minimal, complete verifiable example](http://stackoverflow.com/help/mcve). – Dour High Arch Aug 06 '15 at 15:52
  • 2
    @Adam: Don't ignore the compiler warnings. The code above will cause a warning where the compiler tells you explicitly that *your method will run synchronously*, which is exactly what you're seeing. – Stephen Cleary Aug 06 '15 at 16:03

3 Answers3

1

It looks like you're using asynchronous methods while you really want threads.

Asynchronous methods yield control back to the calling method when an async method is called, then wait until the methods has completed on the await. You can see how it works here.
Basically, the only usefulness of async/await methods is not to lock the UI, so that it stays responsive.

If you want to fire multiple processings in parallel, you will want to use threads, like such:

using System.Threading.Tasks;

public void MainMethod() {
    // Parallel.ForEach will automagically run the "right" number of threads in parallel
    Parallel.ForEach(shipments, shipment => ProcessShipment(shipment));

    // do something when all shipments have been processed
}

public void ProcessShipment(Shipment shipment) { ... }
Community
  • 1
  • 1
thomasb
  • 5,816
  • 10
  • 57
  • 92
  • Thank you for your response, yes I think I have confused the usage of async and await. I will give this a try and see how it comes out, thank you again – Adam Aug 06 '15 at 16:08
  • 1
    @Adam : I have updated my answer because I just remembered `Parallel.ForEach` is awesome. Please use it instead of the horror I wrote before. – thomasb Aug 06 '15 at 16:11
  • 1
    -1 "Asynchronous methods (using async / await) fire a method in a separate thread". No they DON'T! Asynchronous methods in general DO NOT FIRE IN A SEPARATE THREAD. All they do is allow the method to yield control to another method during execution. – Aron Aug 06 '15 at 16:47
  • @cosmo0, `Asynchronous methods (using async / await) fire a method in a separate thread` you are mistaken in that part. The only reason for firing async method on background thread for further back dispatch is UI STA restriction. But such methods are mostly used for some IO operations and there are no additional threads in that case. – Uladzislaŭ Aug 06 '15 at 16:53
  • @Aron : I was pretty sure... I have finally understood something, here. Thanks, and I fixed my post. Let me know if I missed something. – thomasb Aug 07 '15 at 08:11
  • @cosmo0 There is another reason for using async, and that is Threading is REALLY EXPENSIVE. It takes a large chunk of continuous memory to create a stack AND context switching/marshaling is really slow. The ONLY time you ever want to do Threading is when you have something that is CPU bound AND you have an idle CPU. – Aron Aug 07 '15 at 08:15
  • @Aron : ok, but can you fire off multiple async methods in parallel like the `Parallel.ForEach` does (and like the OP wants) ? – thomasb Aug 07 '15 at 08:36
  • @cosmo0 You "CAN" but in most cases, it is actually slower. Imagine you are ordering 10 things on ebay. Currently the OP code, orders 1 item, sits next to the mail box and waits for the item, then orders the next. Your talking about phoning 10 friends and asking them to each other one thing, but DON'T sit next to the mail box. I am saying you should just order them all yourself, because making 10 phone calls is damned slow. – Aron Aug 07 '15 at 08:51
  • Ok, I think I understand. Thanks! – thomasb Aug 07 '15 at 13:38
0

Marking the method as async doesn't auto-magically make it execute in parallel. Since you're not using await at all, it will in fact execute completely synchronously as if it wasn't async. You might have read somewhere that async makes functions execute asynchronously, but this simply isn't true - forget it. The only thing it does is build a state machine to handle task continuations for you when you use await and actually build all the code to manage those tasks and their error handling.

If your code is mostly I/O bound, use the asynchronous APIs with await to make sure the methods actually execute in parallel. If they are CPU bound, a Task.Run (or Parallel.ForEach) will work best.

Also, there's no point in doing .Select(async model => await GetOverview(model). It's almost equivalent to .Select(model => GetOverview(model). In any case, since the method actually doesn't return an asynchronous task, it will be executed while doing the Select, long before you get to the Task.WhenAll.

Given this, even the GetShipmentByStatus's async is pretty much useless - you only use await to await the Task.WhenAll, but since all the tasks are already completed by that point, it will simply complete synchronously.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Thank you for your response. What would I need to change in my method to return an async task? – Adam Aug 06 '15 at 15:55
  • @Adam As I've said, either use asynchronous APIs and `await` in the method, or get rid of the `async` entirely and use `Parallel.ForEach` (or `Task.Run`). – Luaan Aug 06 '15 at 15:58
  • "Given this, even the GetShipmentByStatus's async is pretty much useless" - That's what I gathered by the results and hence the reason I was asking the question and how to fix it. – Adam Aug 06 '15 at 16:12
  • TLDR: The `async` keywords only marks a method to allow you to use the `await` keyword. The `await` keyword allows the program to do something else whilst waiting (technically called `yield`). – Aron Aug 06 '15 at 16:52
0

If your tasks are CPU bound and not I/O bound, then here is the pattern I believe you're looking for:

static void Main(string[] args) {
    Task firstStepTask = Task.Run(() => firstStep());
    Task secondStepTask = Task.Run(() => secondStep());
    //...
    Task finalStepTask = Task.Factory.ContinueWhenAll(
    new Task[] { step1Task, step2Task }, //more if more than two steps...
    (previousTasks) => finalStep());
    finalStepTask.Wait();
}
jacoblambert
  • 787
  • 1
  • 11
  • 18
  • I think I understand how to make my code run in this pattern, I'll give it a shot and let you know how it comes out, thank you for your response – Adam Aug 06 '15 at 16:04
  • 1
    i may have misunderstood your requirement - my example is for many independent processes you can run simultaneously, and one final process that depends on the others finishing. if i did, i'll happily remove the answer. but if you meant that each object that requires a long running task is independent of one another, this could work. good luck. – jacoblambert Aug 06 '15 at 16:20
  • You were correct with you understanding, and I appreciate you giving me an example of a solution I will give it a shot, thanks again – Adam Aug 06 '15 at 16:23
  • -1 There was nothing wrong with his original code for invoking multiple methods to run asynchronously. The problem is his method does not provide the magical keyword `await` which allows it to `yield` control back to the message pump. – Aron Aug 06 '15 at 16:49
  • he confirmed that he wanted the processes to run concurrently, hence the situation would appear more properly done with my example rather than async/await. please see the comments above. – jacoblambert Aug 06 '15 at 16:56
  • We have no way to know whether his tasks are CPU-bound, where threads would be appropriate, or IO-bound, where threads would not be appropriate. The fact he awaits on methods starting with “Get” make me suspect the latter. – Dour High Arch Aug 06 '15 at 17:33