0

This question is not a duplicate. I have already gone through 'Asynchronously wait for Task to complete with timeout' which neither asks nor addresses the issue with Task.Delay(...) inside the task object.

I am writing a piece of code that waits for a condition to become true but I don't want to block the Thread and also doesn't want to eat CPU. And I also want to put a maximum timeout I want to wait before declaring a dead end. Below is how I am trying to do it.

private async Task<bool> WaitForSynchronizedRequest(ClientSynchronizedRequest request, int timeoutInMilliseconds)
{
  bool keepWaiting = true;
  Task task = Task.Factory.StartNew(() =>
  {
    while (keepWaiting)
    {
      if (request.IsCompleted) break;
      Task.Delay(100);
    }
  });

  if (await Task.WhenAny(task, Task.Delay(timeoutInMilliseconds)) != task)
  {
    keepWaiting = false;
  }

  return request.IsCompleted;
}

Task.Delay(...) inside the while loop doesn't wait as expected because of not using async / await but if I use async / await as below then the Task won't wait at all using Task.WhenAny(...) and immediately reports it as completed. Sample code below:

private async Task<bool> WaitForSynchronizedRequest(ClientSynchronizedRequest request, int timeoutInMilliseconds)
{
  bool keepWaiting = true;
  Task task = Task.Factory.StartNew(async () =>
  {
    while (keepWaiting)
    {
      if (request.IsCompleted) break;
      await Task.Delay(100);
    }
  });

  if (await Task.WhenAny(task, Task.Delay(timeoutInMilliseconds)) != task)
  {
    keepWaiting = false;
  }

  return request.IsCompleted;
}

Any suggestion for achieving the Delay with a Timeout as well (without blocking the Thread)?

shashwat
  • 7,851
  • 9
  • 57
  • 90

1 Answers1

3

if I use async / await as below then the Task won't wait at all using Task.WhenAny(...) and immediately reports it as completed

Yeah, because Task.Factory.StartNew doesn't create a task from a task, it creates a task from a synchronous function. Which is why you shouldn't be using it.

There are many ways to create a task, and you actually do use one of them: your compiler. It knows how to build tasks from async functions:

async Task WaitForSynchronizedRequestHelper(ClientSynchronizedRequest request, CancellationToken  ct)
{
  while (!ct.IsCancellationRequested)
  {
    if (request.IsCompleted) break;
    await Task.Delay(100);
  }
}

async Task<bool> WaitForSynchronizedRequest(ClientSynchronizedRequest request, int timeoutInMilliseconds)
{
  var cts = new CancellationTokenSource();

  var task = WaitForSynchronizedRequestHelper(request, cts.Token);

  if (await Task.WhenAny(task, Task.Delay(timeoutInMilliseconds)) != task)
    cts.Cancel();

  return request.IsCompleted;
}

Note that I replaced your patchwork boolean variable with a standard cancellation token.

shashwat
  • 7,851
  • 9
  • 57
  • 90
Blindy
  • 65,249
  • 10
  • 91
  • 131
  • Thanks, @Blindy. It worked like charm! Special thanks for using `CancellationToken` – shashwat May 22 '21 at 14:54
  • 2
    You can further simplify this if you use `CancellationTokenSource.CancelAfter` which cancels the token after a timeout, so you don't need `Task.WhenAny` anymore. All you need then is to await the helper and see if your token was canceled or not after it returns. – Blindy May 22 '21 at 15:14
  • Thanks once again. I am using the constructor `CancellationTokenSource(int millisecondsDelay)` now and removed `Task.WhenAny` thing. Is there a way to not have a separate helper method? – shashwat May 22 '21 at 17:24
  • Yes, but the code is long and ugly if you want to do manually what the compiler does for you. You can make it an inner function in your main function though if that helps! – Blindy May 22 '21 at 18:53