I have the following code that runs without throwing an exception:
var t = Task.Factory.StartNew(() => LongRunningMethod(cancellationToken, progress), cancellationToken);
t.ContinueWith(Callback, TaskScheduler.FromCurrentSynchronizationContext());
Inside 'LongRunningMethod', I call cancellationToken.ThrowIfCancellationRequested(). The callback will always get called (which is what I want), and the Task that gets passed to the callback correctly has IsCancelled set to true or false.
Using the async/await keywords, I have to modify the above lines to the following:
try
{
await Task.Factory.StartNew(() => LongRunningMethod(cancellationToken, progress), cancellationToken);
textEdit1.Text = "Done";
}
catch (OperationCanceledException)
{
textEdit1.Text = "Cancelled";
}
Why, in this case, does the ThrowIfCancellationRequested() throw an actual exception that I need to catch?
With ContinueWith, you're given the Task that was previously run and you can ask it if it cancelled or not (Task.IsCancelled). With await, you don't have that. The only way to communicate cancel is through the exception.
Now, await is simply uses Tasks, so you can "interject" with a continuation. For example:
await Task.Factory
.StartNew(() => LongRunningMethod(cancellationToken, progress), cancellationToken)
.ContinueWith(t=>Trace.WriteLine("Canceled"), TaskContinuationOptions.OnlyOnCanceled);
You can still use await and then use ContinueWith to handle only the cancellation scenario. so, technically the await is awaiting on the continuation.
async was designed to make asynchronous code easier, and as much as possible like the equivalent synchronous code.
First off, note that there was always an exception being thrown. ThrowIfCancellationRequested will (surprise) throw an exception if cancellation has been requested.
In your existing code, this exception is caught and then placed on the Task (wrapped in an AggregateException). The Task interprets this condition as being "canceled". Then you can just check the boolean flag in your continuation.
But consider the equivalent synchronous code:
try
{
LongRunningMethod(cancellationToken, progress);
}
catch (OperationCanceledException)
{
}
And that looks a lot like the async approach. Even if you use ContinueWith, there is still an exception that is thrown and being caught - logically, you are doing a try/catch. Personally, I prefer the explicit try/catch because:
The intent is clearer, so the code is easier to read and maintain.
The code is more accessible (the vast majority of C# programmers understand try/catch; a relative minority understand ContinueWith).
However, ContinueWith is (slightly) more efficient.
Related
I've set up a really simple work service project, with just a single worker (for now). Here's the complete code for the ExecuteAsync method:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
var engine = new CrawlerEngine(_logger);
try
{
await engine.CrawlAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
}
await Task.Delay(TimeSpan.FromMinutes(1));
}
_logger.LogInformation("Worker stopping at: {time}", DateTimeOffset.Now);
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
}
}
As you can see it's fairly simple, just an infinite loop until cancellation and the only real logic in there is all wrapped inside the CrawlerEngine.
Here's the catch, if I run the service it'll run for maybe a couple of minutes and then just exit. It'll not log "Worker stopping" nor an exception, it just plain stops the entire process.
I tried changing all the async code inside CrawlAsync, so it's completely synchronous. This removes the problem and leaves me puzzled.
Every Async method I have inside CrawlAsync returns either Task or Task and is awaited.
So I'm figuring maybe some of the async code throws an exception that isn't caught by the try-catch, however to the best of my knowledge the exception should "bubble up" and be caught as long as you're using await.
I even added an AppDomain.UnhandledException handler, that catches nothing as well.
Do you have any ideas what the heck is going on here?
EDIT: Just for clarification I'm asking whether there's any hidden details to async, that can cause it to crash an entire process with no amount of try-catch being able to see it?
EDIT 2: Seems what's crashing my process is WebClient.DownloadStringAsTaskAsync as well as HttpClient.GetStringAsync. Whenever I use either one, my process just exits at some point. Using a synchronous method for downloading string doesn't cause this.
I finally figured out what was exiting the process: a StackOverflowException.
This explains the lack of logging etc.
I've solved it by changing a recursive method (which called HttpClient.GetStringAsync) to a for loop.
Why it didn't throw StackOverflow when using the non-async variant of GetString is still beyond me though.
This question already has an answer here:
Why does the Task.WhenAny not throw an expected TimeoutException?
(1 answer)
Closed 2 years ago.
I have been reading around a bit, such as here (Catch an exception thrown by an async void method) and I thought the behaviour of async Task methods was:
that you could try/catch them as normal when using await or Task.Wait e.g. try{ await X();}catch(Exception SqlException e){...}
if you let it 'bubble up' you will instead get AggregateException
But I am finding my application is just terminating without breaking on exceptions at all. My code looks like:
internal async Task RunAsync()
{
var tasks = monitors.Select((p) => p.Value.MonitorAsync()).ToList();
await Task.WhenAny(tasks);
Console.WriteLine("ONE");
}
public static async Task Main(string[] args)
{
var app = new App();
try
{
await app.RunAsync();
Console.WriteLine("TWO");
}
catch(Exception e)
{
Console.WriteLine(e);
}
}
Setting breakpoints on "ONE" and "TWO" I can see that tasks has at least one Task with status Faulted, and t has status RanToCompletion. So the fault state is lost and no exceptions.
Clearly I'm missing something obvious, what should I be doing differently?
BTW WhenAny is used to detect unusual termination, these tasks should only ever exit due to failure. This is more of a test-bed to understand catching exceptions.
This is because of WhenAny. Please check the documentation:
The returned task will complete when any of the supplied tasks has
completed. The returned task will always end in the RanToCompletion
state with its Result set to the first task to complete. This is true
even if the first task to complete ended in the Canceled or Faulted
state.
(emphasis mine)
This means that WhenAny will not raise an exception if any of the tasks faults.
This is contrary to WhenAll, which will raise an exception if any of the tasks it waits for faults with an unhandled exception.
Goal:
I am confused by the behavior I am seeing with exceptions in my .Net Core library. The goal of this question is to understand why it is doing what I am seeing.
Executive Summary
I thought that when an async method is called, the code in it is executed synchronously until it hits the first await. If that is the case, then, if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)
Example Code:
Given the following code in a .Net Core Console Application:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
try
{
NonAwaitedMethod();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
Console.ReadKey();
}
public static async Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
await runTask;
}
public static async Task DoStuff(Action action)
{
// Simulate starting up blocking
var blocking = 100000;
await Task.Delay(500 + blocking);
action();
// Do the rest of the stuff...
await Task.Delay(3000);
}
}
Scenarios:
When run as is, this code will throw an exception, but, unless you have a break point on it, you will not know it. The Visual Studio Debugger nor the Console will give any indication that there was an issue (aside from a one line note in the Output screen).
Swap the return type of NonAwaitedMethod from Task to void. This will cause the Visual Studio Debugger to now break on the exception. It will also be printed out in the console. But notably, the exception is NOT caught in the catch statement found in Main.
Leave the return type of NonAwaitedMethod as void, but take off the async. Also change the last line from await runTask; to runTask.Wait(); (This essentially removes any async stuff.) When run, the exception is caught in the catch statement in the Main method.
So, to summarize:
| Scenario | Caught By Debugger | Caught by Catch |
|------------|--------------------|-----------------|
| async Task | No | No |
| async void | Yes | No |
| void | N/A | Yes |
Questions:
I thought that because the exception was thrown before an await was done, that it would execute synchronously up to, and through the throwing of the exception.
Hence my question of: Why does neither scenario 1 or 2 get caught by the catch statement?
Also, why does swapping from Task to void return type cause the exception to get caught by the Debugger? (Even though I am not using that return type.)
exception was thrown before an await was done, that it would execute synchronously
Thought this is fairly true, but it doesn't mean you could catch the exception.
Because your code has async keyword, which turns the method into an async state machine i.e. encapsulated / wrapped by a special type. Any exception thrown from async state machine will get caught and re-thrown when the task is awaited (except for those async void ones) or they go unobserved, which can be caught in TaskScheduler.UnobservedTaskException event.
If you remove async keyword from the NonAwaitedMethod method, you can catch the exception.
A good way to observe this behavior is using this:
try
{
NonAwaitedMethod();
// You will still see this message in your console despite exception
// being thrown from the above method synchronously, because the method
// has been encapsulated into an async state machine by compiler.
Console.WriteLine("Method Called");
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
So your code is compiled similarly to this:
try
{
var stateMachine = new AsyncStateMachine(() =>
{
try
{
NonAwaitedMethod();
}
catch (Exception ex)
{
stateMachine.Exception = ex;
}
});
// This does not throw exception
stateMachine.Run();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}
why does swapping from Task to void return type cause the exception to get caught
If the method returns a Task, the exception is caught by the task.
If the method is void, then the exception gets re-thrown from an arbitrary thread pool thread. Any unhandled exception thrown from thread pool thread will cause the app to crash, so chances are the debugger (or maybe the JIT debugger) is watching this sort of exceptions.
If you want to fire and forget but properly handle the exception, you could use ContinueWith to create a continuation for the task:
NonAwaitedMethod()
.ContinueWith(task => task.Exception, TaskContinuationOptions.OnlyOnFaulted);
Note you have to visit task.Exception property to make the exception observed, otherwise, task scheduler still will receive UnobservedTaskException event.
Or if the exception needs to be caught and processed in Main, the correct way to do that is using async Main methods.
if an exception is thrown during that "synchronous code", why is it not propagated up to the calling method? (As a normal synchronous method would do.)
Good question. And in fact, the early preview versions of async/await did have that behavior. But the language team decided that behavior was just too confusing.
It's easy enough to understand when you have code like this:
if (test)
throw new Exception();
await Task.Delay(TaskSpan.FromSeconds(5));
But what about code like this:
await Task.Delay(1);
if (test)
throw new Exception();
await Task.Delay(TaskSpan.FromSeconds(5));
Remember that await acts synchronously if its awaitable is already completed. So has 1 millisecond gone by by the time the task returned from Task.Delay is awaited? Or for a more realistic example, what happens when HttpClient returns a locally cached response (synchronously)? More generally, the direct throwing of exceptions during the synchronous part of the method tends to result in code that changes its semantics based on race conditions.
So, the decision was made to unilaterally change the way all async methods work so that all exceptions thrown are placed on the returned task. As a nice side effect, this brings their semantics in line with enumerator blocks; if you have a method that uses yield return, any exceptions will not be seen until the enumerator is realized, not when the method is called.
Regarding your scenarios:
Yes, the exception is ignored. Because the code in Main is doing "fire and forget" by ignoring the task. And "fire and forget" means "I don't care about exceptions". If you do care about exceptions, then don't use "fire and forget"; instead, await the task at some point. The task is how async methods report their completion to their callers, and doing an await is how calling code retrieves the results of the task (and observe exceptions).
Yes, async void is an odd quirk (and should be avoided in general). It was put in the language to support asynchronous event handlers, so it has semantics that are similar to event handlers. Specifically, any exceptions that escape the async void method are raised on the top-level context that was current at the beginning of the method. This is how exceptions also work for UI event handlers. In the case of a console application, exceptions are raised on a thread pool thread. Normal async methods return a "handle" that represents the asynchronous operation and can hold exceptions. Exceptions from async void methods cannot be caught, since there is no "handle" for those methods.
Well, of course. In this case the method is synchronous, and exceptions travel up the stack just like normal.
On a side note, never, ever use the Task constructor. If you want to run code on the thread pool, use Task.Run. If you want to have an asynchronous delegate type, use Func<Task>.
The async keyword indicates that the compiler should transform the method to an async state machine, which is not configurable regarding the handling of the exceptions. If you want the sync-part-exceptions of the NonAwaitedMethod method to be thrown immediately, there is no other option than removing the async keyword from the method. You can have the best of both worlds by moving the async part into an async local function:
public static Task NonAwaitedMethod()
{
Task startupDone = new Task(() => { });
var runTask = DoStuff(() =>
{
startupDone.Start();
});
var didStartup = startupDone.Wait(1000);
if (!didStartup)
{
throw new ApplicationException("Fail One");
}
return ImlpAsync(); async Task ImlpAsync()
{
await runTask;
};
}
Instead of using a named function, you could also use an anonymous one:
return ((Func<Task>)(async () =>
{
await runTask;
}))();
What will happen (and why) if the following if statement is satisfied, and Bar() throws an exception?
async Task Foo()
{
Task<object> myTask = Bar();
if (condition)
{
return;
}
else
{
await myTask;
// ....
return;
}
}
Will the exception be caught? By who?
No, the exception won't be caught. You need to specifically add a continuation to the Task (note that when you await a task you're adding a continuation to it).
If Bar throws an exception, it will be thrown right at the point where you call it.
However, if the Task that Bar returns wraps an exception, what happens depends on your version of .NET runtime - for .NET 4.0, it will bring down your entire process, because it eventually causes the exception to be thrown on a finalizer thread (or a thread-pool thread). For .NET 4.5+, the exception will be silently disposed of.
In any case, you don't want either. You should always explicitly handle any asynchronous exceptions that can be propagated in the asynchronous task. If you don't want to await the task in some branch of your code (say, you're pre-loading data you think you'll need, but don't), at least bind a continuation on the task to handle any possible exceptions gracefully.
I know this question has been asked several times, but, I'm looking slightly at a different variant.
public async Task<string> SomeAsyncMethod(string url)
{
// do some URL validation
if (!valid)
{
throw new Exception("some error");
}
// do async stuff now
return await GetFromUrl(url)
}
// now in caller
public async Task<string> SomeOtherAsyncMethod(string input)
{
var task = SomeAsyncMethod(input);
// there is potential chance that a validation fails and
//exception is thrown even before entering async parts of the called function
// do some independent stuff here
try
{
await task;
}
catch(Exception e)
{
// log error
}
// is the following code correct way to handle exceptions?
if (!task.IsFaulted)
{
return task.Result;
}
// log error from task.Exception
return null;
}
In the above code it may so happen that validation fails and exception is thrown even before the control enters async part of the method. Do we need to wrap the first call also around a try..catch block? My experiment showed that this is not useful. Instead, the task status is set to Faulted. So, I believe it is correct to check Task status and return data accordingly. Can C# pros comment on this?
As you have already stated, when you have an async method that throws an exception calling the method will never throw, instead the returned tasks will simply be faulted. This is true even if an exception is thrown before the first await. If that's your desired functionality, then you already have it, and there is no need to change anything.
Do we need to wrap the first call also around a try..catch block?
You may want to do so, as a defensive coding measure. "Precondition" exceptions in async methods suffer from the same problems as they do with enumerator blocks. In the async case, the precondition exceptions are used to fault the task, not raised directly. This is how I do precondition exceptions.
However, there is an alternative. It is possible for an implementation to "eagerly" do the precondition checks and only use faulted tasks to represent asynchronous exceptions. I.e.:
public Task<string> SomeMethodAsync(string url)
{
// do some URL validation
if (!valid)
{
throw new Exception("some error");
}
// do async stuff now
return SomeMethodImplAsync(url);
}
private async Task<string> SomeMethodImplAsync(string url)
{
return await GetFromUrl(url)
}
I don't do this myself, but this kind of approach does have its supporters. Most notably, Jon Skeet.
With that in mind, unless the documentation explicitly specifies that precondition exceptions will be placed on the returned task, you probably should include the call to SomeMethdAsync within a try block.