John
John

Reputation: 405

.NET Task Performance with 1000s of blocked Tasks

I have some .NET4 code that needs to know if/when a network request times out.

Is the following code going to cause a new Thread to be added to the .NET ThreadPool each time a task runs, and then release it when it exits?

var wait = new Task(() =>
{
    using (var pauseEvent = new ManualResetEvent(false))
        pauseEvent.WaitOne(TimeSpan.FromMilliseconds(delay));
}).ContinueWith(action);
wait.Start()

https://stackoverflow.com/a/15096427/464603 suggests this approach would work, but have performance implications for the general system.

If so, how would you recommend handling a high number of request timeouts/s - probably 1000timeouts/s when bursting?

In Python I have previously used something like a tornado IOLoop to make sure this isn't heavy on the Kernel / ThreadPool.

Upvotes: 2

Views: 601

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 456767

I have some .NET4 code that needs to know if/when a network request times out.

The easiest way to do this is to use a timeout right at the API level, e.g., WebRequest.Timeout or CancellationTokenSource.CancelAfter. That way the operation itself will actually stop with an error when the timeout occurs. This is the proper way to do a timeout.

Doing a timed wait is quite different. (Your code does a timed wait). With a timed wait, it's only the wait that times out; the operation is still going, consuming system resources, and has no idea that it's supposed to stop.

If you must do a timed wait on a WaitHandle like ManualResetEvent, then you can use ThreadPool.RegisterWaitForSingleObject, which allows a thread pool thread to wait for 31 objects at a time instead of just one. However, I would consider this a last-ditch extreme solution, only acceptable if the code simply cannot be modified to use proper timeouts.

P.S. Microsoft.Bcl.Async adds async/await support for .NET 4.

P.P.S. Don't ever use StartNew or ContinueWith without explicitly specifying a scheduler. As I describe on my blog, it's dangerous.

Upvotes: 3

Daniel Kozłowski
Daniel Kozłowski

Reputation: 41

First of all, adding Tasks to Thread Pool doesn't necessarily cause new Thread to be added to Thread Pool. When you add a new Task to Thread Pool it is added to internal queue. Existing Threads from Thread Pool take Tasks from this queue one by one and execute them. Thread Pool will start new Threads or stop them as it deems appropriate.

Adding Task with blocking logic inside will cause Threads from Thread Pool to block. It means that they won't be able to execute other Tasks from queue, which will lead to performance issues.

One way to add delay to some action is to use Task.Delay method which internally uses timers.

Task.Delay(delay).ContinueWith(action);

This will not block any Threads from Thread Pool. After specified delay, action will be added to Thread Pool and executed.

You may also directly use timers.

As someone suggested in comment, you may also use async methods. I believe the following code would be equivalent of your sample.

public async Task ExecuteActionAfterDelay()
{
    await Task.Delay(3000);
    action();
}

You might also want to look at this question Asynchronously wait for Task<T> to complete with timeout.

Upvotes: 0

Related Questions