7

I'd like to do something like this:

public async Task<int> DoWork(int parameter) {
    try {
        await OperationThatMayCompleteSynchronously(parameter);
    } catch(Exception) e { 
        if(completedSynchronously)
           doSyncThing();
        else
           doAsyncThing();
    }
}

Note: I'm running tasks on the thread pool, so there is no async context.

I'd like to be able to tell the difference between an exception thrown immediately, and I'm still on the calling thread (e.g. parameter is invalid causing the function to abort), and an exception thrown when the async task completes, and I'm on some other random callback thread (e.g. network failure)

I can work out how I might achieve this if I didn't use await, and just used ContinueWith on the async operation, but is it possible using await?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Orion Edwards
  • 121,657
  • 64
  • 239
  • 328
  • 1
    Keep in mind that if `OperationThatMayCompleteSynchronously` is an async method, all exceptions will be stored in the task and no exceptions will be thrown synchronously – i3arnon Jul 08 '15 at 22:09

2 Answers2

10

Store the task in a variable:

var task = OperationThatMayCompleteSynchronously(parameter); //may throw

Then await it:

await task; //may throw

That way you can differentiate between the two origins for a potential exception.

Note, that async methods never throw directly. They pass exceptions through the task they return. This is true even for "validation" throws that execute before the first await.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    @OrionEdwards, to get the 100% behavior you want, you'd need to amend the above to add something like this: `var task = OperationThatMayCompleteSynchronously(parameter); if (task.IsFaulted) task.GetAwaiter().GetResult(); ...`. Check [this](http://stackoverflow.com/a/21082631/1768303). – noseratio Jul 08 '15 at 23:40
  • 1
    @Noseratio can you explain what that would accomplish? It seems the await does just that. – usr Jul 09 '15 at 09:23
  • AFAIU, the OP wants the 1st line to throw instantly if `OperationThatMayCompleteSynchronously` throws from its synchronous part, rather than at the `await` line. This is not gonna happen if this method is `async`, e.g. `async Task OperationThatMayCompleteSynchronously() { throw new Exception(); }`. It will still throw at the `await` line, @usr. – noseratio Jul 09 '15 at 10:16
  • @Noseratio How does removing the await from the caller changes that? – i3arnon Jul 09 '15 at 10:38
  • @i3arnon, it doesn't alone, but following it with `if (task.IsFaulted) task.GetAwaiter().GetResult()` does, as I mentioned [above](http://stackoverflow.com/questions/31304614/async-await-exception-catching-which-thread-am-i-on/31304646?noredirect=1#comment50600922_31304646). The OP wants *I'd like to be able to tell the difference between an exception thrown immediately, and I'm still on the calling thread*, but it might not be the same thread after `await`, that's why that line. – noseratio Jul 09 '15 at 10:57
  • @Noseratio I see what you're trying to do (though it's not 100% correct as it depends on time and race conditions, but that doesn't really matter). I would use `Task.IsCompleted` instead for this. – i3arnon Jul 09 '15 at 11:05
  • 1
    @usr: Consider amending your answer to also update `OperationThatMayCompleteSynchronously` so that it does its synchronous exceptions in a non-async method. Otherwise, your first line won't actually ever throw. I think that's cleaner than using `IsFaulted`/`IsCompleted`. – Stephen Cleary Jul 09 '15 at 12:19
  • @StephenCleary I think that check would be inherently racy. As I understand it the OP only wants to differentiate between OperationThatMayCompleteSynchronously throwing and the task being faulted. I don't think he is concerned with thread affinity that much (contrary to what his wording suggests).; I make no assumptions about the implementation of OperationThatMayCompleteSynchronously. If he thinks that this method can throw then I assume it's not an async method. – usr Jul 09 '15 at 12:29
  • @i3arnon, the race might be about either hitting `await` or not. Say, the task fails asynchronously on another thread, while the calling thread has executed my `if` check and is about to call `TaskAwaiter.IsCompleted` (or `TaskAwaiter,UnsafeOnCompleted`). The exception will still be thrown on the same calling thread and the same stack frame, which is the desired behavior. – noseratio Jul 09 '15 at 12:33
  • 1
    @usr: Agree re thread identity as a red herring. I would just point out the "wrapper method" explicitly because sometimes people do assume `throw` before `await` will be thrown directly and not placed on the task. And in fact the first Async CTP did have this behavior. – Stephen Cleary Jul 09 '15 at 12:56
1

Instead of awaiting, you can call Wait() or Result, since you claim that there's no synchronization context, and, if the exception caught is AggregateException, it was not thrown in the calling thread.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59