Jim
Jim

Reputation: 16002

async await obtain exception from inner task result

Given the following snippet:

public Task StartReading()
{
  var activityCheck = Task.Factory.StartNew(async () => await this.CheckActivityTimeout(), this._token.Token).Unwrap();
  var reading = Task.Factory.StartNew(async () => await this.ReadAsync(), this._token.Token).Unwrap();

  // for reference, this code produces the same result:
  // var activityCheck = this.CheckActivityTimeout();
  // var reading = this.ReadAsync();

  return Task.WhenAny(reading, activityCheck);
}

When an exception is thrown in CheckActivityTimeout, I am catching it as follows.

var read = StartReading()
var tasks = new Task[] { read, taskx, tasky, taskz };
int completed = Task.WaitAny(tasks);
var r = tasks[completed];

r does not have it's exception set. Instead, if I look at the debugger, I find that the task r has the exception stored within a Result property. How do I get to this actual result?

r has type Id = 17, Status = RanToCompletion, Method = "{null}", Result = "System.Threading.Tasks.UnwrapPromise``1[System.Threading.Tasks.TaskExtensions+VoidResult]"

You can see that the actual exception is inside the result of the inner task. How do I propogate it upwards?

r.Exception == null
r.Result is inaccessible.

update

var r = Task.WhenAny(tasks).Result; // produces exactly the same wrapped result!

In the debugger it looks like this:

enter image description here

Upvotes: 4

Views: 475

Answers (2)

Jim
Jim

Reputation: 16002

I have found that another solution to the problem is just to cast the result to Task<Task>

Casting the result of Task.WhenAny will also obtain the inner task.

 var result = Task.WhenAny(reading, activityCheck).Result;
var inner = ((Task<Task>)result).Result;
inner.Exception...

Upvotes: -1

Stephen Cleary
Stephen Cleary

Reputation: 456417

Your problem is because of how Task.WhenAny works. Task.WhenAny returns a task whose result is the task that completed. This is why read.Result is a task, which in turn contains the exception.

It's not really clear what the desired semantics are, but if you want StartReading to surface the result of the first completed task, you can use a "double await", like this:

public async Task StartReadingAsync()
{
  var activityCheck = this.CheckActivityTimeout();
  var reading = this.ReadAsync();
  await await Task.WhenAny(reading, activityCheck);
}

On a side note, don't use StartNew. If you have CPU-bound (or other blocking code) that you need to move off the UI thread, then use Task.Run; otherwise, just call the methods directly.

Upvotes: 6

Related Questions