3

This is a follow-up to this question. I've also read Stephen Toub's "Tasks and Unhandled Exceptions" and I think I understand how tasks and exceptions work and what "observed task" means. I however cannot figure out how to tell if a Task has been observed or not. Is that possible at all without using reflection?

I'd like to borrow @Noseratio's code as an example:

static async void Observe(Task task)
{        
    await task; 
}

// ...

var taskObserved = false;
var task = DoSomething()
try
{
    bool ready = await DoSomethingElse();
    if (!ready) 
      return null;

    var value = await DoThirdThing(); // depends on DoSomethingElse
    taskObserved = true;
    return value + await task;
 }
 finally
 {
     if (!taskObserved)
        Observe(task);
 }

If we could tell whether the task had been observed, this could be made simpler and more readable:

static async void Observe(Task task)
{        
    if (!task.WasObserved) 
        await task; 
}

// ...

var task = DoSomething()
try
{
    bool ready = await DoSomethingElse();
    if (!ready) 
      return null;

    var value = await DoThirdThing(); // depends on DoSomethingElse
    return value + await task;
}
finally
{
    Observe(task);
}
Community
  • 1
  • 1
avo
  • 10,101
  • 13
  • 53
  • 81

1 Answers1

5

Tasks have no idea whether they've been awaited or not. It's kind of like asking if an integer knows if it's been added to another integer or not.

However, the task's exception can be observed, and whether the exception has been observed is in fact remembered by the task. It's important to distinguish between unobserved exceptions and unawaited tasks. The runtime has some special logic for unobserved task exceptions, but does not do anything special for unawaited tasks.

You really should not write code that depends on whether a task has been awaited. If the semantics for DoSomething are that it always should be awaited even if the result is ignored (a very odd - but technically valid - requirement), then this code should suffice:

var task = DoSomething();
try
{
  bool ready = await DoSomethingElse();
  if (!ready) 
    return null;

  var value = await DoThirdThing(); // depends on DoSomethingElse
  return value + await task;
}
finally
{
  await task;
}

On the other hand, if the semantics of DoSomething are that the task can be ignored if the result isn't needed after all (which is far more likely the case), then this code should suffice:

var task = DoSomething();
bool ready = await DoSomethingElse();
if (!ready) 
  return null;

var value = await DoThirdThing(); // depends on DoSomethingElse
return value + await task;

No need to mess around with worrying about whether a task has been awaited.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks Stephen, I hoped you'd post an answer. Indeed I'm interested in "observed" (rather than "awaited") status. Like if `Task.Exception` has been touched, etc. I have a question about your 1st code fragment though, isn't it going to throw the same exception 2 times? So I'm going to loose the stack frame for the 1st throw? – avo Oct 31 '15 at 08:37
  • 1
    @avo: If `task` completes in a faulted state, then you won't lose the stack frame. The stack is already captured and placed on the `Exception` object in `task.Exception`. `await` is smart enough to preserve the captured stack both times. – Stephen Cleary Oct 31 '15 at 11:04
  • Ideally I'd like to still be able to propogate exceptions thrown from `DoSomethingElse` or `DoThirdThing`, in case either of them throws along with `DoSomething`. However, `DoSomething` takes over those exceptions in this case. – avo Oct 31 '15 at 11:32