1

I would like to run several methods asyncron in a foreach. The return value should be written to a list.

The method is executed in a WPF application. The method GetItemPricesFromJsonAsync fetches from the web data.

public async Task LoadBlackMarketListView(List<MarketAnalysisManager.ItemTier> tiers, List<MarketAnalysisManager.ItemLevel> levels, 
            List<MarketAnalysisManager.ItemQuality> quialityList, string outdatedHours, string profit, Location? location)
        {
            await Task.Run(async () =>
            {
                var blackMarketSellObjectList = new List<BlackMarketSellObject>();

                var items = await MarketAnalysisManager.GetItemListAsync(tiers, levels);

                await Dispatcher.InvokeAsync(() =>
                {
                    PbBlackMarketMode.Minimum = 0;
                    PbBlackMarketMode.Maximum = items.Count;
                    PbBlackMarketMode.Value = 0;
                    GridBlackMarketMode.IsEnabled = false;
                    LvBlackMarket.Visibility = Visibility.Hidden;
                    PbBlackMarketMode.Visibility = Visibility.Visible;
                });

                foreach (var item in items)
                {
                    var allItemPrices = await MarketAnalysisManager.GetItemPricesFromJsonAsync(item.UniqueName, true);
                    if (allItemPrices.FindAll(a => a.City == Locations.GetName(Location.BlackMarket)).Count <= 0)
                    {
                        await IncreaseBlackMarketProgressBar();
                        continue;
                    }

                    blackMarketSellObjectList.AddRange(await GetBlackMarketSellObjectList(item, quialityList, allItemPrices, outdatedHours, profit, location));

                    await IncreaseBlackMarketProgressBar();
                }

                await Dispatcher.InvokeAsync(() =>
                {
                    LvBlackMarket.ItemsSource = blackMarketSellObjectList;
                    PbBlackMarketMode.Visibility = Visibility.Hidden;
                    LvBlackMarket.Visibility = Visibility.Visible;
                    GridBlackMarketMode.IsEnabled = true;
                });
            });

        }

Currently it looks like he's only doing one thing at a time.

Run... 0

End... 0

Run... 1

End... 1

Run... 2

End... 2

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
Triky313
  • 87
  • 2
  • 9
  • 2
    await suspends execution until the awaited task completes, thus making the foreach run synchronously – Tony Abrams May 28 '19 at 11:10
  • Yes that's true, but how do I do it best that several things run in parallel? – Triky313 May 28 '19 at 11:20
  • If you want to continue just using plain async/await check out [my answer](https://stackoverflow.com/a/56341007/10883465). If you want to use the Parallel class check out [this answer by Michal](https://stackoverflow.com/a/56341090/10883465). – Joelius May 28 '19 at 11:27
  • Just something I noticed.. why are you doing await Task.Run(async () => ... in the first line of the function? I don't see any advantage in that and it brings another layer of nesting to the function. If you don't do that and put all the code directly into the function without an annonymous intermediate function, you should get the exact same thing or am I missing something? – Joelius May 28 '19 at 12:23
  • That's correct @Joelius. Thanks for the hint. – Triky313 May 28 '19 at 12:45

1 Answers1

4

You will need to store the Tasks, not await them. Then you can wait for all of them.
Try this (replace your foreach with my code).
I would also advise you to use a real method instead of the annonymous one, it's much more readable.

List<Task> tasks = new List<Task>();
foreach (var item in items)
{
    tasks.Add(Task.Run(async () => 
    {
        var allItemPrices = await MarketAnalysisManager.GetItemPricesFromJsonAsync(item.UniqueName, true);
        if (allItemPrices.FindAll(a => a.City == Locations.GetName(Location.BlackMarket)).Count <= 0)
        {
            await IncreaseBlackMarketProgressBar();
            return;
        }

        blackMarketSellObjectList.AddRange(await GetBlackMarketSellObjectList(item, quialityList, allItemPrices, outdatedHours, profit, location));

        await IncreaseBlackMarketProgressBar();
    }));
}

await Task.WhenAll(tasks);

Note: There is now a return instead of a continue since this is an annonymous function and you just have to end the function there instead of continuing with the foreach.

Joelius
  • 3,839
  • 1
  • 16
  • 36
  • That works great. Unfortunately, the loading time is not shorter. So it does not change much for the user. – Triky313 May 28 '19 at 11:48
  • @Triky313 If you have too many items, then it can even slowdown the program. – Rekshino May 28 '19 at 12:08
  • @Triky313 If this doesn't speed up the process there may be some other dependencies or the time it takes is just very low. I tested it with using an int array as items, turned the awaits to simple delays (Task.Delay) and returned when the int was odd. Doing it my way sped up the code quite a bit compared to doing it your original way. – Joelius May 28 '19 at 12:18
  • @Rekshino could you elaborate on that? Sounds very interesting and I don't quite know what you mean. – Joelius May 28 '19 at 12:18
  • @Joelius See for example [Parallel.ForEach vs Task.Factory.StartNew](https://stackoverflow.com/questions/5009181/parallel-foreach-vs-task-factory-startnew) – Rekshino May 28 '19 at 12:22
  • @Rekshino ahh I see. I only use the Parallel class for a very big amount of items and from my experience it's more common to not reach that threshold. I think you would have to do some mesurements to determine which one performs better in this case. I think either one will work fine. – Joelius May 28 '19 at 12:30
  • @Joelius Okay, it's a bit faster, you're right. This method "MarketAnalysisManager.GetItemPricesFromJsonAsync (item.UniqueName, true)" takes a long time, because data is loaded from the web. Can not I parallelize these 1000 requests ... or does that happen with Task.Run ()? – Triky313 May 28 '19 at 13:36
  • Well what happens is it starts execution but then already starts the next one without waiting for the first one to finish. After the foreach we wait for all the tasks to finish (they are currently running in parallel (check the definiton of parallel when using tasks if you want more information on that)). – Joelius May 28 '19 at 15:17
  • If you think it's not fast enough using this method, you can try to get it to work with the Parallel class. In my experience there are fewer cases (in casual programming) where the Parallel class outperforms my method. Feel free to try the other method though because sometimes it gives you a surprisingly big performance boost. – Joelius May 28 '19 at 15:21
  • @Joelius Is it not possible that a task is re-used? – Ishani Gupta Feb 09 '21 at 02:35