1

I'm working on a code I would like to improve. It is a search method. Based on an input I would like to search this value in multiple tables of my database.

        public async Task<IEnumerable<SearchResponseModel>> Search(string input)
        {
            var listOfSearchResponse = new List<SearchResponseModel>();
            listOfSearchResponse.AddRange(await SearchOrder(input)),
            listOfSearchResponse.AddRange(await SearchJob(input));
            listOfSearchResponse.AddRange(await SearchClient(input));
            listOfSearchResponse.AddRange(await SearchItem(input));
            listOfSearchResponse.AddRange(await SearchProduction(input));
            return listOfSearchResponse;
        }

I use the work await because every search is defined like this one:

public async Task<IEnumerable<SearchResponseModel>> SearchOrder(string input) {...}

My five search methods are not yet really async. They all execute in sequence after the previous one. What should I do from here to make them parallel?

Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
  • 4
    The bottleneck is the I/O the query involve. You can very well parallelize computations; I/O does not scale well. – Olivier Jacot-Descombes Aug 17 '22 at 10:28
  • 1
    [Task.WhenAll](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=net-6.0) should be your friend. you can start them all in parallel and await and harvest the results in the last line with this call. then you can use addrange – Mong Zhu Aug 17 '22 at 10:29
  • Could you include the code inside the `SearchOrder` method? A return value of `Task>` is quite strange. Are you returning a [deferred enumerable](https://stackoverflow.com/questions/7324033/what-are-the-benefits-of-a-deferred-execution-in-linq) or a materialized collection? – Theodor Zoulias Aug 17 '22 at 11:33

2 Answers2

4

I would think that something like this should work, in theory:

var tasks = new[]
            {
                SearchOrder(input),
                SearchJob(input),
                SearchClient(input),
                SearchItem(input),
                SearchProduction(input)
            };

await Task.WhenAll(tasks);

//var listOfSearchResponse = tasks.Select(t => t.Result).ToList();
var listOfSearchResponse = tasks. SelectMany(t => t.Result).ToList();

In practice, it's hard to know how much benefit you'll see.

Bastien Vandamme
  • 17,659
  • 30
  • 118
  • 200
John
  • 3,057
  • 1
  • 4
  • 10
  • 2
    Its work mentioning that [Task.WhenAll](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=net-6.0#system-threading-tasks-task-whenall-1(system-collections-generic-ienumerable((system-threading-tasks-task((-0)))))) will return an `TResult[]`. – Jodrell Aug 17 '22 at 10:48
  • when WhenAll is awaited it returns the resulting items. so there is no need to extra select them in the last line – Mong Zhu Aug 17 '22 at 12:16
  • I just changed the Select by SelectMany – Bastien Vandamme Aug 18 '22 at 02:09
  • It doesn't work. The system spend time on the first line. When I create the array, it uses 3 second. The await line happen in millisecond. – Bastien Vandamme Aug 18 '22 at 02:25
1

It's worth considering using Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

public IObservable<SearchResponseModel> SearchObservable(string input) =>
    Observable.Defer<SearchResponseModel>(() =>
        new []
        {
            Observable.FromAsync(() => SearchOrder(input)),
            Observable.FromAsync(() => SearchJob(input)),
            Observable.FromAsync(() => SearchClient(input)),
            Observable.FromAsync(() => SearchItem(input)),
            Observable.FromAsync(() => SearchProduction(input)),
        }
        .Merge()
        .SelectMany(x => x));

The advantage here is that as each search completes you get the partial results through from the observable - there's no need to wait until all the tasks have finished.

Observables signal each value as they are produced and they signal a finial completion so you know when all of the results are through.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172