0

I have tried to simulate a scenario that when I request my api service once, I have to do 4 IO bounded request to different external API's or databases etc. to get response on my example.

I want to start these requests at the same time as asynchronously and If I get negative response (null for my example) one of these requests, stop all request as soon as I get negative response one of them.

According to my example, the requests was started at the same time and even if I get negative response one of requests I have to wait until long request(task4) finishes. It means I was able to get result at least in 40 second. How can I stop working unnecessarily 20 second more, when I get null response from second request (task2) on 20th second?

If It's possible how I should handle this scenario?

 public class HomeController : Controller
    {
       public async Task<string> SampleAsyncAction()
       {
          var task1 = DoSomeIOAsync(10000);
          var task2 = DoSomeIOAsync(20000);
          var task3 = DoSomeIOAsync(30000);
          var task4 = DoSomeIOAsync(40000);

          var result = await Task.WhenAll(task1, task2, task3, task4);

          if (result.Any(x=> x == null))
                return "Error occurred";
              return "Operation was done successfully in " + result.Sum().ToString() + "millisecond";
       }

       private int? DoSomeIO(int millisecond)
       {
          Thread.Sleep(millisecond);

          if (millisecond == 20000)
             return null;
          return (millisecond);
       }

       private async Task<int?> DoSomeIOAsync(int millisecond)
       {
          return await Task.Run(() => DoSomeIO(millisecond));
       }
    }
guraym
  • 49
  • 2
  • 2
    If you need to simulate async calls, use Task.Delay. don't use Thread.Sleep. Then you can eliminate the Task.Run which is often a code smell. – mason Apr 12 '20 at 15:47
  • If you want to continue when one of the take returns a certain response, then why are you using Task.WhenAll? Did you read the definition of what that does? It's the opposite of what you want. – mason Apr 12 '20 at 15:49
  • Take a look at this: [How can I await an array of tasks and stop waiting on first exception?](https://stackoverflow.com/questions/57313252/how-can-i-await-an-array-of-tasks-and-stop-waiting-on-first-exception) You could use the solutions of this question if you could make your async methods to throw exceptions instead of returning null. – Theodor Zoulias Apr 12 '20 at 17:18
  • Thanks a lot @mason. I know Task.Delay is better but I wanted to simulate working with a function which does not declared async. What if I use something like ORM which does not have async select ability? Then Should not I change sync method which I select something from db to async method by using Task.Run() ? – guraym Apr 12 '20 at 21:14
  • @mason I'm not perfect at async programming. I thought that whenall() is only way I can do some IO request as parallel. What do you advise to me on this scenario ? – guraym Apr 12 '20 at 21:20
  • You are not actually running these asynchronously. You are running them in parallel in 4 different threads - all 4 of those threads are blocked until completion. And then you're waiting asynchronously for all the threads to finish. Creating new threads (i.e. using `Task.Run()`) is generally a bad idea in ASP.NET since ASP.NET has a limited number of threads for the entire application. Once you correct that by actually using asynchronous calls, then this question becomes a duplicate of [the question Theodor linked to](https://stackoverflow.com/a/57313477/1202807). – Gabriel Luci Apr 13 '20 at 00:54

1 Answers1

1

First, only async work can be cancelled, and only if that async work supports cancellation. A sync command like Thread.Sleep is not cancelable, so what you're trying to do here is fundamentally impossible to begin with.

Task.Run is not some magic wand that makes sync suddenly async. All you're doing with that is running the sync code on a new thread. It still must run to completion, and that thread will be tied up until that happens.

Assuming you are actually dealing with async code, and that async code supports cancellation (generally, does it accept a CancellationToken param), then the way to do what you want is with a custom CancellationTokenSource. You create an instance of that, and pass it as the cancellation token to each async method. Then you await each operation and if one of them fails, you send a cancellation to the CTS, which will then cause any operations using that cancellation token to cancel as well.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • 1
    The OP may be satisfied by just canceling the awaiting of the operations, not the operations themselves. Probably not a good idea, but it's possible. – Theodor Zoulias Apr 13 '20 at 15:52
  • 1
    That would be a good way to bring your app to its knees in a web server environment. If the work you're doing is sync/not cancelable, then you're better off just waiting it out than spawning off threads willy-nilly. – Chris Pratt Apr 13 '20 at 16:02
  • @ChrisPratt thanks a lot for response. Just imagine that you are using an ORM which does not have already prepared async method for select. How can you use that sync method asynchronously? Could you give an example please by assuming that the function which I have written above (DoSomeIO) is ORM's sync select function? – guraym Apr 14 '20 at 21:05
  • You can't. If the ORM is sync, then it is sync. You can't just make something async; it has to fundamentally be async top to bottom. – Chris Pratt Apr 14 '20 at 21:06
  • That said, I'm not sure what kind of crazy backwards ORM you're using that's not async. Database access is a fundamentally async thing, so it would be a huge failing of the library to only expose a sync API. – Chris Pratt Apr 14 '20 at 21:09