I am using Steven Toub\'s excellent AsyncPump class that allows console applications to use the async/await keywords.
However, I have a problem where exceptions that
You would probably see the desired behavior if you used async void
signature for MainAsync
, rather than async Task
. This doesn't mean you should change your code (async void
is almost never a good idea), it just means that the existing behavior is perfectly normal.
An exception thrown from async Task
methods is not re-thrown immediately. Rather, it is stored inside the Task
object (with the captured stack context) and will be re-thrown when the task's result gets observed via task.Result
, task.Wait()
, await task
or task.GetAwaiter().GetResult()
.
I posted a bit more detailed explanation of this: TAP global exception handler.
On a side note, I use a slightly modified version of AsyncPump
, which makes sure the initial task starts executing asynchronously (i.e., after the core loop has started pumping), with TaskScheduler.Current
being TaskScheduler.FromCurrentSynchronizationContext()
:
///
/// PumpingSyncContext, based on AsyncPump
/// http://blogs.msdn.com/b/pfxteam/archive/2012/02/02/await-synchronizationcontext-and-console-apps-part-3.aspx
///
class PumpingSyncContext : SynchronizationContext
{
BlockingCollection _actions;
int _pendingOps = 0;
public TResult Run(Func> taskFunc, CancellationToken token = default(CancellationToken))
{
_actions = new BlockingCollection();
SynchronizationContext.SetSynchronizationContext(this);
try
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(
async () =>
{
OperationStarted();
try
{
return await taskFunc();
}
finally
{
OperationCompleted();
}
},
token, TaskCreationOptions.None, scheduler).Unwrap();
// pumping loop
foreach (var action in _actions.GetConsumingEnumerable())
action();
return task.GetAwaiter().GetResult();
}
finally
{
SynchronizationContext.SetSynchronizationContext(null);
}
}
void Complete()
{
_actions.CompleteAdding();
}
// SynchronizationContext methods
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void OperationStarted()
{
// called when async void method is invoked
Interlocked.Increment(ref _pendingOps);
}
public override void OperationCompleted()
{
// called when async void method completes
if (Interlocked.Decrement(ref _pendingOps) == 0)
Complete();
}
public override void Post(SendOrPostCallback d, object state)
{
_actions.Add(() => d(state));
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Send");
}
}
It's also possible to change this part:
return task.GetAwaiter().GetResult();
To this:
return task.Result;
In this case, the exception will be propagated to the caller as AggregateException
, with AggregateException.InnerException
pointing to the original exception from inside the async
method.