AlteGurke
AlteGurke

Reputation: 593

Task.Run vs Task.Factory.StartNew - Expected Deadlock is not happening

I read about the differences of Task.Run and Task.Factory.StartNew.

Task.Run(() => {});

should be equivalent to

Task.Factory.StartNew(() => {}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

But in my code I expect a deadlock which is not happening because of Task.Factory.StartNew:

private Task backgroundTask;

private async Task DoSomethingAsync()
{
   // this should deadlock
   await this.backgroundTask.ConfigureAwait(false);
   throw new Exception();
}

private async Task Test()
{
   this.backgroundTask = Task.Factory.StartNew(async () =>
      {
         await this.DoSomethingAsync().ConfigureAwait(false);
      }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

   // just wait here for testing/debugging
   await Task.Delay(10000).ConfigureAwait(false);
   // if no deadlock, this should throw
   await this.backgroundTask.ConfigureAwait(false);
}

But it is not deadlocking. The exception in DoSomethingAsync is thrown but never catched. Awaiting the Task after the Task.Delay is not throwing either, because it is RanToCompletion.

When using Task.Run it is deadlocking as expected:

private Task backgroundTask;

private async Task DoSomethingAsync()
{
   // this is deadlocking
   await this.backgroundTask.ConfigureAwait(false);
   throw new Exception();
}

private async Task Test()
{
   this.backgroundTask= Task.Run(async () =>
      {
         await this.DoSomethingAsync().ConfigureAwait(false);
      });

   // just wait here for testing/debugging
   await Task.Delay(10000).ConfigureAwait(false);
   // never reached because of deadlock
   await this.backgroundTask.ConfigureAwait(false);
}

Can anybody explain this behaviour?

Upvotes: 4

Views: 1310

Answers (1)

Theodor Zoulias
Theodor Zoulias

Reputation: 43429

The method Task.Factory.StartNew when used with an async delegate returns a nested task: Task<Task>. You are assigning this nested task to a variable of type Task, which is allowed because the class Task<TResult> derives from the class Task. What happens then is that you lose the reference to the inner task, so you have no way of awaiting it. When you await this.backgroundTask you are awaiting the outer Task<Task>, but you can't get the result of the await operation, which is a Task, the inner Task, and await it. The inner task has become a fire-and-forget task.

Upvotes: 5

Related Questions