async/wait: bubbleup exceptions? - c#

This pertains to a program that has to collect some info from a server on the web.
Question: How can I get the exceptions from GetServerResponseAsync and Check to bubble up through CheckAsync to the Main program?
As depicted here, they do not. ErrorHandler never gets hit.
My Main program:
....
try
{
Task.Run(() => CheckAsync());
// bubble to here?
ReadConfiguration();
}
catch (Exception ex)
{
// gather and handle all exceptions here
ErrorHandler.NotifyMe(new[] { "some message" }, ErrorType.Stop); // never gets hit
}
public async Task CheckAsync()
{
await GetServerResponseAsync("slm_check"); // may throw exception
...
if (.....)
throw new Exception("...");
...
}
public async Task GetServerResponseAsync(string command)
{
...
// client = HttpClient()
using (apacheResponse = await client.GetAsync(ServerUrl + "...."))
{
if (....)
throw new Exception("Internal web server error", new Exception("Maybe MySQL server is down"));
using (HttpContent content = apacheResponse.Content)
{
if ( ....)
throw new Exception("error message");
}
}
}

How can I get the exceptions from GetServerResponseAsync and Check to bubble up through CheckAsync to the Main program?
Use await to consume your tasks, instead of ignoring them.
Specifically, this line:
Task.Run(() => CheckAsync());
is getting a Task back from the Task.Run method, which is then ignored. Instead of ignoring that task, the code should be awaiting it:
await Task.Run(() => CheckAsync());
As other commenters have pointed out, the Task.Run here doesn't really make sense. If your operation is asynchronous, it shouldn't need to also run on a background thread. Usually. :) So if you take out the Task.Run, your code would look like:
await CheckAsync();
which will properly propagate the exception.

Related

Exception filter for the class many different methods

In my client application that uses webApi I have a plethora of methods that make calls to webApi asynchronously like this:
var task = Task.Run(async () => await this.SaveObject(User));
return task.Result.Content;
The async function can throw an exception if something is wrong with object being saved. In this case my exception will be handled on the client based on it's type. Problem is when async task throws an exception, task.Result.Content buries in within System.AggregateException.
Right now I handle it like this:
try
{
var task = Task.Run(async () => await this.saveObject(User)); return task.Result.Content;
}
catch(AggregateException ex)
{
throw ex.InnerException;
}
I have too many methods that do it the same way. I was wondering if there is a way to avoid using try/catch block in each and every method. Maybe there is an exception filter mechanism, sorta like used on webApi to catch all exceptions within the class in one place? Maybe some attribute I can mark needed methods with?
First off I would recommend that you don't use .Result on a Task. See https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html or https://montemagno.com/c-sharp-developers-stop-calling-dot-result/.
If you follow the above advice and await a Task within a try block, it will throw the actual exception rather than an AggregateException so you might be able to avoid your rethrow code altogether.
Otherwise, if you really want to stick with your .Result code, you can write a generic wrapper method that does common error handling for you:
try
{
var task = Task.Run(async () => await this.Object(User));
return task.Result.Content;
}
catch(AggregateException ex)
{
throw ex.InnerException;
}
To something like:
return RunAsync(() => this.Object(User));
private T RunAsync<T>(Func<Task<T>> func)
{
try
{
var task = Task.Run(func);
return task.Result;
}
catch(AggregateException ex)
{
throw ex.InnerException;
}
}
EDIT:
I've just realised there's another way (see http://blog.stephencleary.com/2014/12/a-tour-of-task-part-6-results.html) which is slightly more "hacky" since it feels more hidden but this:
var task = Task.Run(async () => await this.Object(User));
return task.GetAwaiter().GetResult().Content;
The .GetAwaiter().GetResult() will synchronously wait for the Task (as per .Result) but will not wrap any thrown exceptions in an AggregateException - which seems to be your desire.

Thrown Exception not caught in UnitTest try/catch

I'm designing some code right now where I'm throwing an exception if a string parameter is null or empty and the exception is thrown as it should be, but it isn't getting caught when I'm UnitTesting.
Here's the client I'm using.
public class PipeClient : IPipeClient
{
public async void Send(string host, string pipeName, Message msg)
{
if (string.IsNullOrEmpty(msg.PreparedMessage))
throw new ArgumentException("MESSAGE_NOT_FOUND");
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(pipeName))
throw new ArgumentNullException();
if (!host.TryParseHost())
throw new ArgumentException("INVALID_HOST_NAME");
using (var pipeClient = new NamedPipeClientStream(host, pipeName, PipeDirection.Out))
{
pipeClient.Connect(200);
using (var writer = new StreamWriter(pipeClient))
{
await Task.Run(() => writer.WriteLine(msg.PreparedMessage));
writer.Flush();
}
}
}
}
And here's the UnitTest
[TestMethod]
public void Send_FailsOnWrongHostName()
{
var name = "FailWithHostname";
var msg = new Message(MyStates.Register, "UnitTest", "Test");
try
{
var client = new PipeClient();
client.Send("lol", name, msg);
}
catch (Exception e)
{
Assert.IsTrue(e is ArgumentException);
}
}
So when I run that test it should as far as I know throw the exception when I call the the Send method (which is does) and then get caught in the catch clause because I'm not catching it inside the PipeClient. Yet it doesn't, it just exits with a failed test.
If you need any more information just let me know, thanks in advance.
there's a few things I want to raise in this answer. I'm not sure of your experience level so please don't think I'm being condescending at any point.
Firstly a brief note on async methods and Tasks.
Async void should be avoided unless in an async event handler. Async methods should return Task or Task otherwise there is nothing for the calling method to keep hold of to know when the method is done and to report back whether the method threw an exception. Async void is essentially fire and forget, there is no one left to observe the exceptions.
"In observed Tasks no one can you scream" -Me ,2018
Exceptions thrown in async methods are nicely unwrapped and thrown
when the async method is awaited, with the call stack all preserved
and reasonably sensible. If you don't await the result eventually at
some point in the future you will get an UnobservedTaskException
that, if you haven't configured a global handler for, will bring down
your application. If you get the result of an async method
synchronously using .Wait() or .Result or via
.GetAwaiter().GetResult() (all 3 you should try and avoid but the 3rd
option is best if you have to I have been informed), then you will
get the original exception wrapped in an AggregateException.
Now if none of this is making much sense to you, I would recommend doing some reading up Tasks and async/await.
Now onto your Test.
Your method is async void so there is nothing for the calling method to have returned to it to represent the work or to let it know that the method has thrown an exception. So it carries on, the test finishes and then everything completes with no exceptions because the UnobservedTaskException can be thrown at anypoint in the future (I think it is related to when the garbage collector tidies up the faulted Task and then it throws and because the garbage collector is non-deterministic we can't say when that will happen)
So what if you made your async method return a Task??? Well that's still not quite right. You are now returning a Task that will be in a faulted state because of the exception, however because you never await it, the exception is never 'unwrapped' and actually thrown and so you're test happily continues.
What you need to do is make your Test async and return a Task and make the method you're testing async Task not async void and await that method in your test.
Like this
[TestMethod]
public async Task Send_FailsOnWrongHostName()
{
var name = "FailWithHostname";
var msg = new Message(MyStates.Register, "UnitTest", "Test");
try
{
var client = new PipeClient();
await client.Send("lol", name, msg);
}
catch (Exception e)
{
Assert.IsTrue(e is ArgumentException);
}
}
public class PipeClient : IPipeClient
{
public async Task Send(string host, string pipeName, Message msg)
{
if (string.IsNullOrEmpty(msg.PreparedMessage))
throw new ArgumentException("MESSAGE_NOT_FOUND");
if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(pipeName))
throw new ArgumentNullException();
if (!host.TryParseHost())
throw new ArgumentException("INVALID_HOST_NAME");
using (var pipeClient = new NamedPipeClientStream(host, pipeName, PipeDirection.Out))
{
pipeClient.Connect(200);
using (var writer = new StreamWriter(pipeClient))
{
await Task.Run(() => writer.WriteLine(msg.PreparedMessage));
writer.Flush();
}
}
}
}

Async Await Equivalent

When I run this code everything works fine:
public async void InvokePlugin(MyObject xTask)
{
try
{
var hndlr = new TimeoutHandler(RunTask);
var asyncResult = hndlr.BeginInvoke(xTask, null, new object());
if (!asyncResult.AsyncWaitHandle.WaitOne(xTask.Timeout, false))
{
throw new TimeoutException("Plugin didn't complete processing in a timely manner.");
}
hndlr.EndInvoke(asyncResult);
}
catch (Exception ex)
{
//Handle Exceptions
}
}
private delegate void TimeoutHandler(MyObject xTask);
I want to update this code to use Async/Await. I tried doing it like this:
public async void InvokePlugin(MyObject xTask)
{
try
{
var runTask = Task.Run(() => { RunTask(xTask); });
if (await Task.WhenAny(runTask, Task.Delay(xTask.Timeout)) == runTask)
{
// Task completed within timeout.
// Consider that the task may have faulted or been canceled.
// We re-await the task so that any exceptions/cancellation is rethrown.
await runTask;
}
else
{
throw new TimeoutException("Plugin didn't complete processing in a timely manner.");
}
}
catch (Exception ex)
{
//Handle Exceptions
}
}
...but it's not working. Clearly I'm doing somethign wring. It does call the RunTask Method and executes the first 2 lines fine but then it just ends and I can't seem to catch the exception in either the TaskRun method or code above. All I see in the Output windows is "Program has exited with code 0 (0x0)."
If the experts out there can either point me to what I'm doing wrong or give me suggestions as to how I can catch the exception and handle it I would be very grateful.
Also if you feel I missed any important details please ask and I will update my question.
Usually I'd say if it works don't fix it but in this case I'm trying to rearchitect a bit to allow for some enhancements so here I am.
Change async void to async Task. See my article on async best practices for more information.
After you do this, consume it asynchronously:
await the task, allowing async to grow.
async and await will naturally grow upward through your code base, until they reach Main, which cannot be async.
In your Main method, call GetAwaiter().GetResult() on the "top" task.
Blocking on asynchronous code is generally not a good idea, but blocking on a single task in a Console app's Main method is an exception to that rule.

Strange try/catch block behavior

I am working with Github API via Octokit.net. Now I am writing code that responsible for TwoFactorAuth. When I send request for token, if it is 2FA account, than I must get "TwoFactorRequiredException". I am trying to catch it, but insteand of it I get "AggregateException". But when I look in InnerExceptions of Agg..Exc.. then I have only one, and it is "TwoFactorRequiredException".
Here is my code:
try
{
return client.Authorization.Create(newAuthorization).Result.Token;
}
catch ( Octokit.TwoFactorRequiredException )
{
_navigationService.NavigateAsync($"{nameof(TwoFactorAuthPage)}", animated: false);
}
//catch ( AggregateException ex )
//{
// foreach ( var exception in ex.InnerExceptions )
// {
// Debug.WriteLine(exception.GetType());
// }
//}
When it is commented, then I have AggregateExceptions, when it is uncommented, then I have AggExc and inside it I have TwoFactor..Exception.
That's because client.Authorization.Create(newAuthorization) returns a Task<T>, where T is your result type. Inside a Task, multiple exceptions can occur, that is why the Task Parallel Library returns an AggregateException capturing all the exceptions.
Instead of synchronously blocking on an async method, you should await on it, which asynchronously waits for it's completion. You gain two things from this. The first is that you don't synchronously block and can free the thread, and the second is that await unwraps the exception for you:
public async Task<Token> CallSomethingAsync()
{
try
{
var result = await client.Authorization.Create(newAuthorization);
result.Token;
}
catch ( Octokit.TwoFactorRequiredException )
{
_navigationService.NavigateAsync($"{nameof(TwoFactorAuthPage)}", animated: false);
}
}
Although I'm not sure what NavigateAsync does, I think you may also want to await on it's completion as well.

Exception is not caught at Cancelation of Task.Run

I have a class Worker which is doing some work (with simulated workload):
public class Worker
{ ...
public void DoWork(CancellationToken ct)
{
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested();
Thread.Sleep(2000);
}
}
Now I want to use this method in a Task.Run (from my Windows Forms App,at button-click) which can be cancelled:
private CancellationTokenSource _ctSource;
try
{
Task.Run(() =>
{
_worker.DoWork(_ctSource.Token);
},_ctSource.Token);
}
catch (AggregateException aex)
{
String g = aex.Message;
}
catch (OperationCanceledException ex)
{
String g = ex.Message;
}
catch (Exception ex)
{
String g = ex.Message;
}
But when the task is started, I can't cancel it with _ctSource.Cancel();
I get an error in visual studio that the OperationCanceledException is not handled!
But I surrounded the Task.Run Call in a try-catch-clause! The Exception which ocurrs in the Worker object should thrown up or not?
What is the problem?
Your Task.Run call creates the task and then returns immediately. It doesn't ever throw. But the task it creates may fail or be canceled later on.
You have several solutions here:
Use await:
await Task.Run(...)
Attach a continuation depending on the failure/cancellation case:
var task = Task.Run(...);
task.ContinueWith(t => ..., TaskContinuationOptions.OnlyOnCanceled);
task.ContinueWith(t => ..., TaskContinuationOptions.OnlyOnFaulted);
Attach a single continuation on failure:
Task.Run(...).ContinueWith(t => ..., TaskContinuationOptions.NotOnRanToCompletion);
The solution you can/should use depends on the surrounding code.
You need to new the token
private CancellationTokenSource _ctSource = new CancellationTokenSource();
Why are throwing an expectation in DoWork?
Exception from one thread don't bubble up another thread that started the thread.
Cancellation in Managed Threads
If a parallel Task throws an exception it'll return execution and will have it's Exception property (as an AggregateException, you should check for its InnerException) set (and either its IsCanceled or IsFaulted property set to true). Some minimal sample code from a project of mine which escalates the exception to the main thread:
var t = new Task(Initialize);
t.Start();
while (!t.IsCompleted && !t.IsFaulted)
{
// Do other work in the main thread
}
if (t.IsFaulted)
{
if (t.Exception != null)
{
if(t.Exception.InnerException != null)
throw t.Exception.InnerException;
}
throw new InvalidAsynchronousStateException("Initialization failed for an unknown reason");
}
If you use a CancellationTokenSource it should be easy to enhance this to check for IsCanceled (instead of IsFaulted)
You can also use Task.Wait() instead of the while loop... in my project and in that precise case it seemed more appropiate to use the while loop, but you need to wait for the Task to end in one way or another.
If you use Task.Run() you can use a .ContinueWith(Task) which will have the original task passed in (where you can check for IsFaulted or IsCanceled), or have it run only on faulted execution, at your will.

Categories

Resources