Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

await Task.Delay takes longer than expected

I wrote a multithreaded app which uses async/await extensively. It is supposed to download some stuff at a scheduled time. To achieve that, it uses 'await Task.Delay'. Sometimes it sends thousands requests every minute.

It works as expected, but sometimes my program needs to log something big. When it does, it serializes many objects and saves them to a file. During that time, I noticed that my scheduled tasks are executed too late. I've put all the logging to a separate thread with the lowest priority and the problem doesn't occur that often anymore, but it still happens. The things is, I want to know when it happens and in order to know that I have to use something like that:

var delayTestDate = DateTime.Now;
await Task.Delay(5000);
if((DateTime.Now - delayTestDate).TotalMilliseconds > 6000/*delays up to 1 second are tolerated*/) Console.WriteLine("The task has been delayed!");

Moreover, I have found that 'Task.Run', which I also use, can also cause delays. To monitor that, I have to use even more ugly code:

var delayTestDate = DateTime.Now;
await Task.Run(() =>
{
  if((DateTime.Now - delayTestDate).TotalMilliseconds > 1000/*delays up to 1 second are tolerated*/) Console.WriteLine("The task has been delayed!");
  //do some stuff
  delayTestDate = DateTime.Now;
});
if((DateTime.Now - delayTestDate).TotalMilliseconds > 1000/*delays up to 1 second are tolerated*/) Console.WriteLine("The task has been delayed!");

I have to use it before and after every await and Task.Run and inside every async function, which is ugly and inconvenient. I can't put it into a separate function, since it would have to be async and I would have to await it anyway. Does anybody have an idea of a more elegant solution?

EDIT:

Some information I provided in the comments:

As @YuvalItzchakov noticed, the problem may be caused by Thread Pool starvation. That's why I used System.Threading.Thread to take care of the logging outside of the Thread Pool, but as I said, the problem still sometimes occur.

I have a processor with four cores and by subtracting results of ThreadPool.GetAvailableThreads from ThreadPool.GetMaxThreads I get 0 busy worker threads and 1-2 busy completion port threads. Process.GetCurrentProcess().Threads.Count usually returns about 30. It's a Windows Forms app and although it only has a tray icon with a menu, it starts with 11 threads. When it gets to sending thousands requests per minute, it quickly gets up to 30.

As @Noseratio suggested, I tried to play with ThreadPool.SetMinThreads and ThreadPool.SetMaxThreads, but it didn't even change the numbers of busy threads mentioned above.

like image 244
humanista Avatar asked Apr 19 '14 15:04

humanista


People also ask

What happens if you don't await a task?

If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.

Does await halt execution?

The await expression is usually used to unwrap promises by passing a Promise as the expression . This causes async function execution to pause until the promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment.

What is difference between task and async await?

async, await, and TaskThe await keyword waits for the async method until it returns a value. So the main application thread stops there until it receives a return value. The Task class represents an asynchronous operation and Task<TResult> generic class represents an operation that can return a value.

What is Task delay in C#?

The Delay method is typically used to delay the operation of all or part of a task for a specified time interval. Most commonly, the time delay is introduced: At the beginning of the task, as the following example shows. C# Copy.


2 Answers

When you execute Task.Run it uses Thread Pool threads to execute those tasks. When you have long running tasks, you are causing starvation to the Thread Pool, since its resources are currently occupied with long running tasks.

2 Suggestions:

  1. When running long running tasks, make sure to use Task.Factory.Startnew with TaskCreationOptions.LongRunning, which will trigger a new thread creation. You must be cautious here as well, as spinning too many new threads will cause excessive context switches which will cause your app to slow down

  2. Use true async where you have to do IO Bound work, use apis that support the TAP such as HttpClient and Stream, which wont cause a new thread to execute blocking work.

like image 82
Yuval Itzchakov Avatar answered Sep 29 '22 13:09

Yuval Itzchakov


There are overheads in async/await, as well as the tasks themselves being executed at a lower priority. If you need something to happen reliably at an accurate interval, async/await / TPL is not the interface to use.

Try creating an independent background thread that loops until it is scheduled to do work. This way you can control the priority and timing directly without going through TPL / async.

Thread backgroundThread = new Thread(BackgroundWork);
DateTime nextInterval = DateTime.Now;

public void BackgroundWork()
{
    if(DateTime.Now > nextInterval){
        DoWork();
        nextInterval = nextInterval.Add(new TimeSpan(0,0,0,10)); // 10 seconds
    }
    Thread.Sleep(100);
}

Adjust the Sleep(..) and interval values as needed.

like image 31
K893824 Avatar answered Sep 29 '22 13:09

K893824