19

I have a web server and there is a periodic job merges and send records (lots of request logs).

Task.Run(() =>
{
    while (true)
    {
        try
        {
            MergeAndPutRecords();
        }
        catch (Exception ex)
        {
            Logger.Error(ex);
        }
    }
});

In MergeAndPutRecords function, there are code merging records and async function returns Task sending records. (Actaully it is Amazon Kinesis Firehose's PutRecordBatchAsync.)

Then what happens if i call that function without await keyword? Does function runs on seperated thread? Here says it is not. Then what is returning Task Means? Here says async method without await keyword means

  1. Starts the asynchronous method on the current thread. Ignores all results (including exceptions).

Then my periodic job and PutRecordBatchAsync are processed concurrently? I know asynchronouse and concurrent is different. But there is no await keyword and they are in same thread. Which one would be executed first? I'm confusing...

There would be massive records that needs to be merge and sent in real-time. So I think it must be executed concurrently..

gunr2171
  • 16,104
  • 25
  • 61
  • 88
YB Yu
  • 352
  • 1
  • 3
  • 9
  • 4
    `async` keyword doesn't do anything by itself. Each `await` gets combined into a `AsyncStateMachine` that can process stuff asynchronously. If you don't have `await` keyword inside `async` method then there is no reason to create `AsyncStateMachine`, so you get synchronous method. – FCin Dec 22 '17 at 08:56
  • 4
    `await` is used to *wait for a result to arrive*. It does not, in any way, control *how the process to obtain the result was **started*** or how the ongoing process to obtain the result is working. – Damien_The_Unbeliever Dec 22 '17 at 08:57

2 Answers2

8

Then my periodic job and PutRecordBatchAsync are processed concurrently?

Using Task API you can ensure that they are executed concurrently (using Thread pool), but you need to understand the difference between in memory concurrent operations Vs IO based concurrency.

While In memory concurrency does benefit using Tasks, IO call once executed doesn't need thread at all, as it relies on hardware concurrency, if it ever use the thread, all that it would do it wait for the IO call to return, thus wasting the precious system resources and reducing system scalability

You case is that IO based concurrency, as you call a remote / network based API, how does async-await helps here ?

Well true Async operation will free up the thread context, on windows it would use IO completion port (queuing mechanism) to execute the Async call, while the calling thread is used to dispatch other similar calls, it would just need thread context on return of the IO call for serving the response and for that too, if its not a UI call, then use ConfigureAwait(false), so that any thread context can be used to deliver the response.

What if you don't use await with async ?

The call meant to be Asynchronous becomes Synchronous and would immediately impact the system scalability, as threads are now blocked, even worse for a long running IO operations. Have you seen how JavaScript frameworks always make a AJAX (Async) call to the server API, thus much more work is possible W/o blocking the Browser threads.

In general for In memory processing you would create certain number of Tasks and process them using Task.WaitAll or Parallel.ForEach for a collection, for Async processing, ideally recommendation is not to have a Task.Run anywhere, its preferred to have to Async from the entry point, like its possible in case of MVC, controllers can be async. Multiple calls are grouped together using Task.WhenAll representative Task and then awaited upon. even if you use Task.Run as in your code, then use async lambda to execute an asynchronous call

Summary :

Its mandatory to use await for asynchronous calls, else they async keyword is useless in that context and yes await will wait for the IO call to return before continuation is executed, though no thread is blocked in the process

Mrinal Kamboj
  • 11,300
  • 5
  • 40
  • 74
  • 2
    "The call meant to be Asynchronous becomes Synchronous and would immediately impact the system scalability, as threads are now blocked" - I believe this is either incorrect or unclear. Yes, the call to the async function returns synchronously, but conceptually it always did; the asynchronicity "happens" at the await statement. If await doesn't exist, the caller proceeds past the asychronous function out of order. If the Task has a continuation, it still runs, but is effectively headless; results and exceptions are ignored. – shannon Jun 14 '19 at 15:25
  • (1/3)Let's understand the statement in the right context, essence of asynchronous processing for the IO calls is shifting the processing to the background W/o blocking any process thread waiting for the background process to finish, thus ensuring much higher Scalability, since in windows every execution takes place on a Thread and idle wait is highly undesirable. What `await` does is create a state machine, which captures the current execution context, which is used for continuation post return. In a chain of method calls actual Async processing begins when calls leaves the process boundary – Mrinal Kamboj Jun 15 '19 at 10:14
  • (2/3) Before that we expect every call to me marked with `async-await`, when in reality they are still run on a thread waiting to shift in the background or using hardware based concurrency for a call made over network. Now here if we break the `async-await` at any point, then benefits will not be forthcoming, that's why it has to be from entry point onward (Main method). `Task` continuation is different that use thread pool thread, it's not pure async. Only other use case for the Async processing is where UI thread is to be freed, but that still use the thread pool thread for logic processing – Mrinal Kamboj Jun 15 '19 at 10:18
  • 1
    (3/3) Thus, most genuine case for Async processing remains IO processing and here we can use `ConfigureAwait(false)` to ensure that returning call can execute on any available thread, instead of waiting for same thread context for reentry and that's a performance optimization. Also Exception and Result are not ignored but are available on explicit access of `Task`, which is by design in the .Net 4.5, it was different in the earlier versions, when exceptions used to propagate automatically like any other logical code – Mrinal Kamboj Jun 15 '19 at 10:21
  • 2
    I don't understand the "benefit" you are saying will be lost. If we know the method need not be awaited, we don't reduce scalability by not awaiting it. Note the question is about an async method, not an async call. If an async method takes out the trash and is guaranteed to be complete in less than an hour, and the return value is how the trash smelled when it was taken out, it's quite acceptable to "fire and forget" the task once every week if we feel it meets our requirements. – shannon Jun 17 '19 at 12:14
  • If you don't await anywhere in the chain, benefits of async are not forthcoming, that's why it's recommended to await all calls from entry point onwards. Once it's synchronously blocked, it cannot scale, since it's blocking thread pool thread, which defeats the purpose of IO async. "Fire and forget" is not the same as synchronous wait, and use case for fire and forget are few and limited, mostly in case of dispensable logging or auditing, anywhere status/result is awaited, it cannot be Fire and forget – Mrinal Kamboj Jun 17 '19 at 17:11
  • I think you shall review the relation of async await and scalability to understand the impact. Most of the dynamic languages like Python, Nodejs just rely on async processing by default to achieve massive scalability. Task continuation to fire and forget is very different, that was utilized frequently before advent of async await. Nowadays continuations can be set of consecutive await calls, but they don't block thread pool thread like plain vanilla `Task.Wait` or `Task.Result` – Mrinal Kamboj Jun 17 '19 at 17:22
  • 1
    @MrinalKamboj the OP is not implying the use of `Task.Wait()` or `Task.Result` instead of using `await`. Rather, they are asking what happens if `await` is elided from the code. If `await` is elided, there is no blocking or scaling issue and the task executes at some point in the future when the `TaskScheduler` gets around to it. It effectively becomes a "fire and forget" task whose exceptions go to the `TaskScheduler.UnobservedExceptions` and are ignored. – Dave Black Aug 28 '20 at 17:19
  • @DaveBlack role of `await` in an async method is to free up thread-pool thread used to initiate execution. By removing await the same call will be synchronous, since the calling Pool thread cannot be released, there's no state machine saving the context. This will not block the program, and looks like fire and forget but you are still blocking the critical resource, which will prevent the system to scale up. On windows it will use the web server thread pool, which will be constrained as all its threads would be blocked waiting for a remote call to return – Mrinal Kamboj Aug 30 '20 at 09:27
  • @DaveBlack a true fire and forget operation would be where you fire, it immediately returns and call is sent to remote server to execute, now this may be success or failure, but we are not waiting for it. Mostly there's a queue in the Background, which manage the execution of such methods, while there's no notification expected by the caller – Mrinal Kamboj Aug 30 '20 at 09:30
  • 2
    @MrinalKamboj the ThreadPool is not used unless you use `Task.Run()` with it. Eliding `await`starts the asynchronous method on the current thread. Ignores all results (including exceptions). - see here: https://stackoverflow.com/a/46245804/251267 – Dave Black Sep 01 '20 at 17:16
  • @DaveBlack you are misunderstanding the answer provided by Stephen, in windows everything runs on a Thread (here background or thread Pool). Idea is to use same thread or request a separate thread from a Pool, on asp.net all threads are from same pool, but until and unless there's `await` there's no way to release a thread, which begins execution, best way to test is to run a stress test and figure out yourself – Mrinal Kamboj Sep 02 '20 at 07:46
0

If a method returns a Task, it is always best practice to observe the result of that Task at some point. It may be the declared return value, or it may be an exception. If you want to observe that task before your method continues, await is the recommendation.

In this case, you should probably be observing the result of the Task returned by PutRecordBatchAsync. You will want to know if the call failed for any reason as this probably indicates that your records were not stored!

In the example code you gave, you will find that you make subsequent calls to MergeAndPutRecords before the previous call is complete. Are you sure that this is intended?

Gusdor
  • 14,001
  • 2
  • 52
  • 64