2

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.

Community
  • 1
  • 1
John
  • 405
  • 4
  • 19
  • 2
    You could use an asynchronous api for your network requests. That way you need just one thread. – Haukinger Aug 16 '16 at 11:29
  • using the `async` and `await` apis is highly recommended in such a case. They are implemented using co-routines and are much lighter weight than what you're doing here. – Mgetz Aug 16 '16 at 12:00
  • 1
    Indeed. I am using `net4` though and I believe these were introduced in `.net 4.5` – John Aug 16 '16 at 12:20

2 Answers2

3

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the comprehensive answer and article link. I'm in application-layer network land, so its not a straightforward network request. The request will (in most circumstances) always complete but then, a few seconds later, trigger a push from the remote back to the client. `ThreadPool.RegisterWaitForSingleObject` looks good but I will avoid if possible. Where did you get the `31` figure from? – John Aug 17 '16 at 09:19
  • @John: There's a Win32 API that allows one thread to wait for up to 32 handles. One is used for thread control (adding/removing handles or killing the thread), leaving 31 handles per thread pool thread. – Stephen Cleary Aug 17 '16 at 12:30
0

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.

Community
  • 1
  • 1
  • Indeed, but as I understand it, adding many blocked Task to a Thread Pool may cause many new threads to be spun up. AFIK `Task.Delay` and `async`/`await` isn't avaiable in `.net4` – John Aug 16 '16 at 12:23
  • Starting a task doesn't add a `Task` to the `ThreadPool`. Tasks can be executed by threads in the threadpool, but that is not always the case. – Maarten Aug 16 '16 at 14:53