I have an ActionBlock that simply processes messages that comes from an infinite loop continuously. Inside ActionBlock I make an http post. When on any network related error, the method throws exception and the block is faulted/stopped. This is not the behavior that I want. I want processing runs even though exception occurs. (Keep hitting Process method) To simulate my program;
private static ExecutionDataflowBlockOptions processBlockOptions
{
get
{
return new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1
};
}
}
static async Start()
{
processQueue = new
ActionBlock<QueueMessage>(
async (item) =>
{
await Process(item);
},
processBlockOptions);
while (!Stopped)
{
//Read from DB and do logic with item
QueueMessage item= new QueueMessage();
await processQueue.SendAsync(item);
}
}
private async static Task<int> Process(QueueMessage item)
{
try
{
await item.HttpPost(order);
}
catch (Exception ex)
{
//Http endpoint might be broken
throw ex;
}
}
You're re-throwing your exceptions, and you're doing it wrong:
throw ex;
If you need to log the errors or stop the pipeline for a while, you don't need to throw anything, simply log ex.ToString() and react accordingly. Second thing is that you should use throw; instead of throw ex;, as you're rewriting the stack trace for exception, which doesn't really matter in this case but can be misleading in case of more complicated workflow.
Related
I am implementing a retry mechanism for an API call to make the same request if the stated business logic occured. I made it really easy for now and will enchance it later with exceptionType handling etc. I simply throw exception if the logic happens and catch them in the retry mechanism but in below the code is just killing the thread and does not execute after the first try. Can you help me please what I am missing here ?
This is the retry logic I am trying to use.
public static T Do<T>(Func<T> action, TimeSpan retryInterval, int attemptCount)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < attemptCount; attempted++)
{
try
{
if (attempted > 0)
{
Thread.Sleep(retryInterval);
}
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
This is how I invoke the retry method.
await Task.Run(() =>
{
RetryHelper.Do(() => ConfirmRequestRetryAsync(request, true), TimeSpan.FromSeconds(60), 10);
});
And this is the method which can throw exceptions due to logic.
public async void ConfirmRequestRetryAsync(ConfirmRequest request, bool flag)
{
logger.Info($"Confirm Request Async Called for the request : {JsonConvert.SerializeObject(request)}");
var confirmRequest = GetSignedConfirmRequest(request.PaymentId);
var confirmResponse = await MakeRequest(confirmRequest);
//Added flag and sending at false at first try to not throw exception
//Then in retry mechanism this exceptions will be use to trigger retry logic.
if (flag)
{
var statu = ConfirmResponseXmlConvert(confirmResponse);
if (statu.Item1 == "0" && statu.Item2 == "InProcess")
{
throw new Exception("InProcess");
}
else if (statu.Item1 == "-1" && statu.Item2 != "Declined")
{
throw new Exception("Error");
}
}
}
The ConfirmRequestRetryAsync is an async void method. This means it will return at the first await. The rest of the code will run at some later time, in the same thread context as the caller. So when the exceptions are thrown the retrying method has already returned and there is nothing to catch the exceptions.
The fix is to make it an async Task method, and await this in your retry-method. This might require two variants of the retrying method, one for async methods and one for non-async.
A rule of thumb for async void is to never allow exceptions to be thrown from them, since there is no possibility for anyone to catch them. Always use async Task if exceptions is a possibility.
There are some tasks that I attached an action to. I have a middleware for observing all of the exceptions. Now, there is a problem with thread exception. I've already thrown an exception manually in body of the action, but when I throw an exception the application goes to break mode state and I can't monitor the exceptions.
Before mvc in configure, I put my error handling middleware
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var code = HttpStatusCode.InternalServerError;
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
//and this is the piece of code that run all tasks.
foreach (var item in BotList)
{
BotHandler BotObject = new BotHandler(item,conn);
Task.Run(() => { BotObject.Run();});
}
//
public void Run()
{
//BotClient.StopReceiving();
BotClient.OnMessage += BotOnMessageReceived;
BotClient.OnMessageEdited += BotOnMessageReceived;
BotClient.OnCallbackQuery += BotOnCallbackQueryReceived;
}
private async void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
{
try
{
//do something
string a = null;
var b = a.ToString();
}
catch(Exception exp )
{
throw exp
}
}
}
As I understood you run this code in action of controller:
//and this is the piece of code that run all tasks.
foreach (var item in BotList)
{
BotHandler BotObject = new BotHandler(item,conn);
Task.Run(() => { BotObject.Run();});
}
The main problem is that you are trying to run the task for an already finished request. That is why ExceptionHandlingMiddleware (and actually other middlewares) can't handle anything. To fix your issue you can add try/catch block to handle an unexpected exception.
I would strongly advise not to start a background task during an HTTP request. It's error-prone approach as a task may shut down at any time and you won't even notice. Instead of this approach, it's better to use background task (msdn, SO Discussion), some kind of AWS lambda/Azure function or another task scheduler.
In case you need to do some recalculation job after HTTP method is called you may consider async message processing to trigger the process.
if you go here https://msdn.microsoft.com/en-us/magazine/jj991977.aspx you will read that.
Exceptions from an Async Void Method Can’t Be Caught with Catch
So you cannot catch exception (the middleware cannot) which you are throwing in your BotOnMessageReceived method (in a Catch part).
So you have 2 solutions.
Remove async keyword
Or Catch app unhandled exceptions if its possible. For example in normal .net framework console app, you have event in App called unhandled exceptions, and can handle such situations like yours.
I have also found smth here, maybe it will help
How do I catch unhandled exceptions in ASP .NET Core 2.0 before the page is rendered?
Problem
Several tasks are run in parallel, and all, none, or any of them might throw exceptions. When all the tasks have finalized, all the exceptions that might have happened must be reported (via log, email, console output.... whatever).
Expected behavior
I can build all the tasks via linq with async lambdas, and then await for them running in parallel with Task.WhenAll(tasks). Then I can catch an AggregateException and report each of the individual inner exceptions.
Actual behavior
An AggregateException is thrown, but it contains just one inner exception, whatever number of individual exceptions have been thrown.
Minimal complete verifiable example
static void Main(string[] args)
{
try
{
ThrowSeveralExceptionsAsync(5).Wait();
}
catch (AggregateException ex)
{
ex.Handle(innerEx =>
{
Console.WriteLine($"\"{innerEx.Message}\" was thrown");
return true;
});
}
Console.ReadLine();
}
private static async Task ThrowSeveralExceptionsAsync(int nExceptions)
{
var tasks = Enumerable.Range(0, nExceptions)
.Select(async n =>
{
await ThrowAsync(new Exception($"Exception #{n}"));
});
await Task.WhenAll(tasks);
}
private static async Task ThrowAsync(Exception ex)
{
await Task.Run(() => {
Console.WriteLine($"I am going to throw \"{ex.Message}\"");
throw ex;
});
}
Output
Note that the output order of the "I am going to throw" messages might change, due to race conditions.
I am going to throw "Exception #0"
I am going to throw "Exception #1"
I am going to throw "Exception #2"
I am going to throw "Exception #3"
I am going to throw "Exception #4"
"Exception #0" was thrown
That's because await "unwraps" aggregate exceptions and always throws just first exception (as described in documentation of await), even when you await Task.WhenAll which obviously can result in multiple errors. You can access aggregate exception for example like this:
var whenAll = Task.WhenAll(tasks);
try {
await whenAll;
}
catch {
// this is `AggregateException`
throw whenAll.Exception;
}
Or you can just loop over tasks and check status and exception of each.
Note that after that fix you need to do one more thing:
try {
ThrowSeveralExceptionsAsync(5).Wait();
}
catch (AggregateException ex) {
// flatten, unwrapping all inner aggregate exceptions
ex.Flatten().Handle(innerEx => {
Console.WriteLine($"\"{innerEx.Message}\" was thrown");
return true;
});
}
Because task returned by ThrowSeveralExceptionsAsync contains AggregateException we thrown, wrapped inside another AggregateException.
In the method below, when an exception is thrown in the TRY block, it is being swallowed. How can I make it throw the exception so that it gets written to log in the catch block? The log writer works fine. Thanks!
public static bool MonitorQueueEmptyTask(string queueName, CancellationTokenSource tokenSource)
{
try
{
Task<bool> task = Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!"); //THIS THROW IS SWALLOWED -- NO LOG WRITTEN ON CATCH
};
return true;
}, tokenSource.Token);
}
catch (Exception ex)
{
WriteExceptionToLog(ex.Stack); //it's not that this method doesn't work. it works fine.
return false;
}
return true;
}
If you want to fire and forget, you can attach a continuation using ContinueWith. The current try-catch will not help you at all, as the exception is encapsulated inside the Task. If this is "fire and forget", than you can log the exception:
public static Task MonitorQueueEmptyTask(
string queueName, CancellationTokenSource tokenSource)
{
return Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!");
};
}, tokenSource.Token, TaskCreationOptions.LongRunning).ContinueWith(faultedTask =>
{
WriteExceptionToLog(faultedTask.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
This, in turn, will not propagate the exception after it's thrown, but will provide a mechanism to log the error. If you want the exception to be properly handled, you can register to TaskScheduler.UnobservedTaskException. Additionally, you can set ThrowUnobservedTaskExceptions enabled="true" in your configuration if you want unhandled exceptions to terminate your application. ContinueWith will consider the exception "handled" once you look at the task.Exception property.
The exception is not swallowed; it's just that it doesn't occur on the thread that executes the try/catch block, but on the separate Task thread.
If you don't observe the task's result or exception, when the task is eventually garbage collected, it will throw an exception saying that the task was not observed. Unless you catch that by handling the TaskScheduler.UnobservedTaskException, it will crash the process.
I also had a problem with this, and i really dislike the whole idea of App.config, so can provide another solution to prevent the exceptions disappearing :)
Save the exception then throw it after the Task.Run has completed, e.g.
private async void Function() {
Exception save_exception = null;
await Task.Run(() => {
try {
// Do Stuff
} catch (Exception ex) {
save_exception = ex;
}
}).ContinueWith(new Action<Task>(task => {
if (save_exception != null)
throw save_exception;
// Do Stuff
}));
}
I hope you can help me with that problem.
I have a method that does specific actions, for example, I am sending a HttpWebRequest. There I can get a WebException, so I put it in a Try-Catch-block to rethrow exceptions for more specific exception messages.
Like that: (This is in a method called doWebRequest)
try
{
// HttpWebRequest here
}
catch (WebException ex)
{
throw new WebException(String.Format("My special additional message {0}", ex.Message);
}
Well, so this works, when I call this function normally.
But now I want an async way to do this. What I made to call this method in an async method:
public void DoRequestAsync()
{
Task internalRequest = new Task(doWebRequest);
internalRequest.ContinueWith(InternalUpdateSearchExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
internalRequest.Start();
}
So this makes a new task and then calls the method doWebRequest async.
To handle errors now, because I know it is different on async, I made this handler, which you can also see in internalRequest.ContinueWith. Looks like that:
private void InternalUpdateSearchExceptionHandler(Task task)
{
var ex = task.Exception;
if (ex.InnerException is WebException)
{
if ((ex.InnerException as WebException).Status == WebExceptionStatus.ProtocolError)
{
throw new WebException(ex.InnerException.Message);
}
else
{
throw new Exception("There was no response from the server.");
}
}
}
But this is not executing any exceptions. I don't know why.
At first I thought, this is because it cannot take the InnerException as a WebException, or would that work? If not, please tell me what to do here. But even when I throw an exception without any queries here, it did not throw any exceptions while debugging. Why is that?
Help is appreciated. When something is not clear, ask me. ;)
Your continuation is throwing an exception, but when that continuation throws an exception all that happens is the Task that represents that continuation is marked as Faulted with the given exception as its Exception. You currently are ignoring the task created by calling ContinueWith, so there is nothing to observe this exception.
DoRequestAsync likely shouldn't be void; rather it should return a Task, specifically the one created by calling ContinueWith, so that whoever calls this method can observe any exceptions thrown.
Also note that you could do this much more simply using async:
public async Task DoRequestAsync()
{
try
{
var content = await new WebClient()
.DownloadStringTaskAsync("address");
}
catch (WebException ex)
{
throw new WebException(String.Format("My special additional message {0}", ex.Message);
}
}