4

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

3
  • 2
    Not sure if this is related, but Task.Factory.StartNew does not support async delegates. Use Task.Run instead. Commented Feb 4, 2016 at 12:38
  • 2
    Also, any reason why you are creating a new Task to call the async methods like CheckActivityTimeout? why not calling them directly? Commented Feb 4, 2016 at 12:39
  • Changing the code to use Task.Run has the same result. Commented Feb 4, 2016 at 12:44

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

6 Comments

So await await Task.WhenAny(reading, activityCheck); is the up voted answer, hmm? I would have thought there should only be one await -- like my answer calls out.
@DavidPine: await await observes the inner task exception. That is, the inner await gets whichever task completed first (this await never fails); then the outer await observes the results of that task, propagating any exceptions.
Thanks for sharing! I learned something today... It is kind of ugly though.
@DavidPine: Yeah, it's a tossup. You can alternatively do await Task.WhenAny(...).Unwrap(); which some people prefer. Either way, this is a rare use case.
Since you could chain .Unwrap, could you also have await await await await ..., that could chain as well?
|
-1

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...

1 Comment

stack overflow should not allow downvotes without a comment, this works just fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.