-1

I have two async methods that both return a List. They both search for records in a different way, and i need them both to run in parallel, and i only care about the task that finishes first. The other one should be canceled if possible.

I presume i should be using Task.WhenAny but don't seem to be getting the implementation correct. The below GoAsync implementation doesn't return any records, it jumps out of the routine (no errors) at await Task.WhenAny(taskList) line. I get no results, etc. How can i get the results? Am i going about this correctly?

public static async void GoAsync(SearchContract search, CancellationToken cancellationToken = default(CancellationToken))
        {
            var taskList = new List<Task>();

            taskList.Add(new Task(async () => await SearchRoutine1Async(search, cancellationToken)));
            taskList.Add(new Task(async () => await SearchRoutine2Async( search, cancellationToken)));

            Task completedTask = await Task.WhenAny(taskList);
        }

The Async routines are something like this:

 public static async Task<List<SearchModel>> SearchRoutine1Async( SearchContract search, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (DBContext db = new DBContext)
            {
                var searchModels= await db.SearchModel
                    .Where(sm => sm.subKey1 = search.subKey1)
                    .ToListAsync(cancellationToken)
                    ; 

                return searchModels;
            }
        }

When i've tried running these tasks individually such as doing this: It works fine.

var callTask = Task.Run(() => SearchRoutine1Async(search));
callTask.Wait();
var list1 = callTask.Result;

var callTask2 = Task.Run(() => SearchRoutine2Async(search));
callTask2.Wait();
var list2 = callTask.Result;

[UPDATE]: I've changed my GoAsync to this: according to one of the answers below: (but it's still not working)

public static async Task<List<SearchModel>> GoAsync(SearchContract search, CancellationToken cancellationToken = default(CancellationToken))
        {

            var taskList = new List<Task<List<SearchModel>>>
            {                    
                SearchRoutine1Async(search, cancellationToken),
                SearchRoutine2Async(search, cancellationToken)
            };

            Task<List<SearchModel>> completedTask = await Task.WhenAny(taskList);

            return completedTask.Result;
        }

I call the routine from a SYNCHRONOUS routine as such :

var result = GoAsync(search);

Note i'm deep in a routine that where i need to run these task in parallel in order to get a competing result. That routine as a signature of private static void DoVariousCalucualtions() {}

user1161137
  • 1,067
  • 1
  • 11
  • 31
  • 1
    Cast `completedTask` to `Task>` ?? Why `async void` ???? – ta.speot.is Nov 27 '18 at 03:29
  • Tasks are (still) not threads and async is not parallel: https://www.wintellect.com/tasks-are-still-not-threads-and-async-is-not-parallel https://code-maze.com/csharp-delegates/ – jazb Nov 27 '18 at 03:29
  • *it jumps out of the routine (no errors) at await Task.WhenAny(taskList) line* Well yeah, you made it asynchronous ?????? – ta.speot.is Nov 27 '18 at 03:56
  • @ta.speot.is ... well if i add a line of code after that await Task.WhenAny(taskList) line ... like var result = completedTask.Result it never reaches that line either... it just jumps out of routine. – user1161137 Nov 27 '18 at 07:00
  • Yes because it's asynchronous. It will come back to that method when it's ready to continue. `GoAsync` should be awaited, but failing that, you need to at least have something keeping your app alive until all the async tasks have completed. – ta.speot.is Nov 27 '18 at 07:03
  • well i need GoAsync to be the one that comes back with the result.. it needs to await in there. How do i do that. I can change the async void to return a Task> but the calling routine is a synchronous method. – user1161137 Nov 27 '18 at 07:11
  • @user1161137 the entire `taskList.Add(new Task(async () => await ` isn't needed and only creates problems. You could just write `var finisher=await Task.WhenAny(SearchRoutine1Async(search, cancellationToken),SearchRoutine1Async(search, cancellationToken))`. – Panagiotis Kanavos Nov 27 '18 at 11:36
  • 1
    @user1161137 `async void GoAsync` is a bug, it should be `async Task GoAsync()`. `async void` is only meant for event handlers. `async void` methods can't be awaited and may still be running when an application or request terminates. If you *don't* want it to run asynchronously use `Task.WaitAny` – Panagiotis Kanavos Nov 27 '18 at 11:37
  • @PanagiotisKanavos yes, i've changed my routine to look like the one by Fabio below... but still can't get it to work. I'm going to update what i'm doing in my post. – user1161137 Nov 27 '18 at 14:27
  • What do you mean by "not working"? – Evan Trimboli Nov 27 '18 at 21:13
  • @EvanTrimboli i'm not able to get any result... GoAsync exists and in the calling routine.. it just continues. I'm going to test this where is entire async.. if it works, i'll open another ticket regarding the synchronous call part. – user1161137 Nov 27 '18 at 21:43

1 Answers1

1

You don't need to create new Task, asynchronous method will return a task.

public static async Task GoAsync(
    SearchContract search,
    CancellationToken cancellationToken = default(CancellationToken))
{
    var taskList = new List<Task<List<SearchModel>>>
    {
        SearchRoutine1Async(search, cancellationToken),
        SearchRoutine2Async( search, cancellationToken)
    };

    Task<List<SearchModel>> completedTask = await Task.WhenAny(taskList);
    // use competedTask.Result here (safe as you know task is completed)
}

After question update:

To get results you need await for the function to complete.

var result = await GoAsync();

But you mentioned that this call located deep in the synchronous function with void signature and you are not using ASP.Net Core. That means you need to make whole "methods pipeline" asynchronous starting from controller action. If you will use GoAsync().Result; you will end up with a deadlock, behaviour you already experienced...

Fabio
  • 31,528
  • 4
  • 33
  • 72
  • This doesn't work for me... i don't get a completedTask... i try doing a var result = completedTask.Result after the WhenAny... but it never gets to that line of code.. it just exists out after the hitting Task> completedTask = await Task.WhenAny(taskList); – user1161137 Nov 27 '18 at 06:47
  • @user1161137, exiting after hitting `await Task.WhenAny...` is correct behaviour of `async-await`, if you debugging code, put a breakpoint to the line `var result = completedTask.Result;` and run application in the debug mode. If "Routine" methods works correctly breakpoint should be hit. – Fabio Nov 27 '18 at 07:18
  • So i changed the signature to public static async Task> GoAsync(...) and i added the line after the WhenAny, but it does not hit that breakpoint. I still can't seem to get the actual result. GoAsync was being called by a synchronous routine, thats why i first tried the GoAsync with a VOID. Can you please fix your example so i may understand this better? – user1161137 Nov 27 '18 at 07:41
  • @user1161137, show how you call `GoAsync` method and in what environment you are executing your code? If you execute `GoAsync().Result` not in ASP.NET Core application you probably getting a deadlock. – Fabio Nov 27 '18 at 08:21
  • I have updated my portion according to what i'm doing. Still unable to get the result. This is not asp.net core... just asp.net. – user1161137 Nov 27 '18 at 14:39
  • @user1161137, I am afraid you need to make asynchronous all methods stack up to the controller. – Fabio Nov 27 '18 at 19:57
  • According to Stephen Cleary i seem to be able to use the library Nito.AsyncEx to use var task = SearchRoutine1Async(search, cancellationToken); var result = task.WaitAndUnwrapException(); which works, but it doesn't seem to work when i call the async routine that wraps both tasks and calls WhenAny. https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c – user1161137 Nov 27 '18 at 21:11
  • the problem i'm having has to do with the fact that i am calling this from a synchronous routine.. when i move this to an entirely async pipeline, it works as intended. Seems that the skipping out of the routine mentioned above only happens when the call is made from a synchronous routine too. I have tried Stephen Cleary's solution A, but it doesn't seem to work on the task array. Only when passing in one async routine at a time. I'll open another ticket to see if anyone knows how to resolve that. – user1161137 Nov 27 '18 at 22:44
  • the SearchRoutine1Async also needed to use .ConfigureAwait(false) in order to resolve correctly. – user1161137 Nov 27 '18 at 22:49