sanosdole
sanosdole

Reputation: 2489

Task inlining and Task.Wait

I just realized that when i start a task from within a task and call Task.Wait the new task will not be inlined, while calling Task.Result will always inline the task.

As we wrap our tasks with a RAII pattern (implemented in ExecuteWithCancel), inlining will reuse allocated resources and is preferable.

But we sometime want to wait a certain time and cancel the task after that. The waiting code looks like this:

using (var cts = new CancellationTokenSource())
{
   // Task scheduler decides whether to execute synchronous or asynchronous
   var task = new Task<TResult>(() => ExecuteWithCancel<TResult>(cts.Token, nameOfTaskPerformer, arguments), cts.Token)
   if (timeout==TimeSpan.Zero || task.Wait(timeout)) // this creates an all or nothing timeout
      return task.Result;
   cts.Cancel();
   throw new TimeoutException("");
}

When timeout is TimeSpan.Zero the Task is inlined, otherwise it always uses another thread.

Is there an easy way to redesign this code to use inlining and waiting/timeout?

Upvotes: 1

Views: 2014

Answers (1)

Ohad Schneider
Ohad Schneider

Reputation: 38106

Pretty sure that's not possible. Suppose you are running the following code on thread A:

var task = Task.Factory.StartNew(() => Thread.Sleep(Timeout.Infinite));
task.Wait(5000);

If the task is inlined, thread A will block indefinitely - how will it wake up after the timeout? Looking at the reference source (Task.cs) we can see exactly that:

internal bool InternalWait(int millisecondsTimeout, CancellationToken cancellationToken) 
{
   ...
   // we will attempt inline execution only if an infinite wait was requested 
   // Inline execution doesn't make sense for finite timeouts and if a cancellation token was specified
   // because we don't know how long the task delegate will take. 
   if (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled &&
       WrappedTryRunInline() && IsCompleted) 
   {
       returnValue = true; 
   } 
   else
   { 
       returnValue = CompletedEvent.Wait(millisecondsTimeout, cancellationToken);
   }

As per your question, in order to benefit from inlining with finite timeouts, you'd have to implement the timeout logic inside the task itself, perhaps something like:

ExecuteWithCancel<TResult>(cts.Token, TimeSpan timeout, nameOfTaskPerformer, arguments)

And then use a regular Wait() (or Result).

Upvotes: 3

Related Questions