Are a .NET Task thread's resources returned back to the pool temporarily if the thread is waiting on an async operation to finish?

前端 未结 3 1373
再見小時候
再見小時候 2021-01-11 12:32

I have a TPL Task that does two things. First, it calls a web service. Second, it inserts some data into a database. I have up to 20 Tasks started at one time doing this sam

相关标签:
3条回答
  • 2021-01-11 12:49

    The answer is yes. Although technically it's not "waiting" for the async operation to complete (otherwise there would be no benefit to async). Under the hood there is a callback delegate that is run when the async operation completes, which is what allows your calling thread to proceed without blocking. It's the async/await magic that turns these 'continuations' into a linear looking piece of code.

    Because you are using a threadpool thread, when it hits an await the thread will return to the threadpool. The thing to be careful with here is that the normal behaviour is when the awaited operation completes it will try to get back onto the thread that it was started on (now probably being used by another Task) so you may observe latency problems in getting the results back as threadpool threads are now tied up starting other tasks. Over time the threadpool will try to adjust the number of available threads to meet demand but you might find this doesn't happen quickly enough if you work comes in bursts. The result will be apparently poor performance as you may only have a small number of threads available.

    0 讨论(0)
  • 2021-01-11 13:01

    If all an application's tasks block, each task will use a thread from the thread pool.

    If all tasks regularly await, the thread pool does not need to use a thread for every task.

    When your code awaits an operation that hasn't completed yet, the method's state is saved such that it can be resumed on any other thread.

    Idle threadpool threads get released after a while, so the actual thread which hits an await can be released from the thread pool while the method calling await is still running.

    Putting all this together, an async version of a routine can do the same work with less threads (assuming the workload has enough balance of time awaiting vs spinning the CPU).

    This code runs 100 tasks doing a synchronous wait:

    var numTasks = 100;
    for (int i = 0; i < numTasks; i++)
    {
        Thread.Sleep(5);
        Task.Run(() =>
        {
            Thread.Sleep(5000);
            Interlocked.Decrement(ref numTasks);
        });
    }
    while (numTasks > 0) Thread.Sleep(100);
    

    For the async wait, change it to:

        Task.Run(async () =>
        {
            await Task.Delay(5000);
            Interlocked.Decrement(ref numTasks);
        });
    

    On my system the async version grows the peak thread count half as much, and takes 20% of the time to do the same 'work'.

    0 讨论(0)
  • 2021-01-11 13:07

    Would the thread pool have more threads at its disposal if I made the service call and database call with async and await() instead of making them blocking calls?

    It depends on what you mean by "making use of async-await".

    When you use Task.Run, behind the scenes, the Task class uses the ThreadPool to offload work using a ThreadPool thread.

    If your service doesn't expose a true async api and you uses Task.Run to queue your work, you will still be blocking a threadpool thread to do IO bound work, regardless of the use of async-await. In your question you state that both calls are blocking calls, and in that case the answer is no, the threadpool thread used to make those blocking calls woul still be blocked.

    If your service and database calls were true async APIs (one that doesn't consume any extra threads to do its work), you could advantage of async-await, as when you await on one of those calls (and you shouldn't need to use Task.Run with them at all), the current thread will yield control back to the caller, and can be used in the meanwhile to do more work. If this is the case, then yes.

    My theory (and I'm not sure why I think this) is that the thread is busy doing nothing while waiting on the blocking web service and can't return its resources temporarily to the pool. But I wonder if the Tasks were waiting for async calls to finish whether the main Task thread would be able to switch to let other stuff process while waiting.

    Your theory is correct. If the main job of the queued threadpool work is to make an IO bound request then its spending of most its time simply blocking until the request finishes.

    When you await a Task, control yields back to caller. Lets assume your service call was a REST call, you could use HttpClient which exposes true non-thread consuming async methods such as GetAsync, PostAsync, and when you await these calls, your calling thread is released to do more work in the meanwhile.

    0 讨论(0)
提交回复
热议问题