Xamarin: Exceptions raised from tasks are not propagated - c#

I have the following code in Xamarin (tested in ios):
private static async Task<string> TaskWithException()
{
return await Task.Factory.StartNew (() => {
throw new Exception ("Booo!");
return "";
});
}
public static async Task<string> RunTask()
{
try
{
return await TaskWithException ();
}
catch(Exception ex)
{
Console.WriteLine (ex.ToString());
throw;
}
}
Invoking this as await RunTask(), does throw the exception from the TaskWithException method, but the catch method in RunTask is never hit. Why is that? I would expect the catch to work just like in Microsoft's implementation of async/await. Am I missing something?

You cant await a method inside of a constructor, so thats why you can't catch the Exception.
To catch the Exception you must await the operation.
I have here two ways of calling an async method from the constructor:
1. ContinueWith solution
RunTask().ContinueWith((result) =>
{
if (result.IsFaulted)
{
var exp = result.Exception;
}
});
2. Xamarin Forms
Device.BeginInvokeOnMainThread(async () =>
{
try
{
await RunTask();
}
catch (Exception ex)
{
Console.WriteLine (ex.ToString());
}
});
3. iOS
InvokeOnMainThread(async () =>
{
try
{
await RunTask();
}
catch (Exception ex)
{
Console.WriteLine (ex.ToString());
}
});

Related

Captured variable is disposed in outer scope

I have the following piece of sample code to which resharper complains "Captured variable is disposed in outer scope". I do not any issues here as ExecuteAsync which calls the unnamed lambda is awaited so httpClient will not be used outside the scope. Is this a false positive?
private static async Task MyTestFunction(IHttpClientFactory httpClientFactory) {
string baseUrl = "http://localhost:8000";
using var httpClient = httpClientFactory.CreateClient();
try {
await ExecuteAsync(
async () => {
try {
await httpClient.GetAsync(new Uri(baseUrl)).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex);
throw;
}
}).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw;
}
}
private static async Task ExecuteAsync(Func<Task> func) {
await func().ConfigureAwait(false);
}
It's complaining that httpClient won't be disposed until ExecuteAsync finished because it's declared early. It could be disposed earlier if you declare it within the inner try statement.
Note: this will probably have very little performance benefits if any, ReSharper sticks to a rigid set of rules for things that it flags.
It's also a good idea to make your base url a const variable as it never changes.
private const string BaseUrl = "http://localhost:8000";
private static async Task MyTestFunction(IHttpClientFactory httpClientFactory)
{
try {
await ExecuteAsync(
async () => {
try {
using var httpClient = httpClientFactory.CreateClient();
await httpClient.GetAsync(new Uri(BaseUrl)).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex);
throw;
}
}).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw;
}
}
private static async Task ExecuteAsync(Func<Task> func) {
await func().ConfigureAwait(false);
}

Catch exception of a async method

Hello I want to catch a Exception of a async method but it did not work look a this example :
public void TryToCatchException()
{
try
{
_ =LongRunningMethod();
}
catch (MyException)
{
Console.WriteLine("catched");
}
}
public static async Task LongRunningMethod()
{
await Task.Run(() =>
{
try
{
Task.Delay(1000); //simulation of work
throw new ArgumentException(); // this is a example
}
catch (ArgumentException)
{
throw new MyException;
}
});
}
if I launch the debugger will say that the exception "MyException" is NOT catched... can someone help me ?
When you discard tasks, those exceptions are not observed:
_ =LongRunningMethod();
If you want to catch exceptions from LongRunningMethod, then you need to await the task returned from that method:
await LongRunningMethod();

Using BeginTransactionAsync() in different functions that also implement it

I have something similar to this:
public async Task Task1()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await OtherTask();
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
The problem that I'm having is that when I call OtherTask() and it tries to do the BeginTransactionAsync(), it says that one transaction already exist. Is there anyway I can fix this? I want to be able to call multiple functions, that when executed alone can have their own transaction, but when being called from a function which already has a transaction, to use that instead.
The easiest way to fix this is to refactor the logic into a separate method:
public async Task Task1()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
//Code goes here
await OtherTask(transaction);
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask(Transaction transaction)
{
//Code goes here
}
public async Task OtherTask()
{
await using var transaction = await _context.Database.BeginTransactionAsync();
try
{
await OtherTask(transaction);
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
I am assuming you are using EntityFramework Core.
DbContext.Database has a property CurrentTransation that will be null if neither BeginTransactionAsync nor UseTransaction have been called before.
public async Task Task1()
{
await using var transaction = _context.Database.CurrentTransaction == null
? await _context.Database.BeginTransactionAsync()
: _context.Database.UseTransaction(_context.Database.CurrentTransaction);
try
{
//Code goes here
await OtherTask();
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}
public async Task OtherTask()
{
await using var transaction = _context.Database.CurrentTransaction == null
? await _context.Database.BeginTransactionAsync()
: _context.Database.UseTransaction(_context.Database.CurrentTransaction);
try
{
//Code goes here
await transaction.CommitAsync();
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
}

Simple general exception handling in async code without boilerplate

We start using CancellationToken in out app a lot, so we have to change exception handling correspondingly:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await DoJob(cts.Token);
Console.WriteLine("Successfully finished");
}
private static async Task DoJob(CancellationToken ct)
{
try
{
await Task.Delay(1000, ct);
}
catch (Exception e) when(!(e is OperationCanceledException))
{
Console.WriteLine("Do cleanup in case of error.");
}
}
}
The idea behind this code is that if someone use catch(Exception e) (please do not blame me for this) and forgot to exclude CancellationToken, an error handling is executed, for example, there is a log that operation failed. But it is not true, id doesn't fail, it just has been canceled. And cancellation should be handled differently then failure.
It seems to me like a big boilerplate to write practically in every general catch
catch (Exception e) when(!(e is OperationCanceledException))
Is there some more robust solution with less boilerplate?
You could create a method that accepts a Func<Task> and catches the exception(s), e.g.:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await GeneralDoJobAndCatchException(() => DoJob(cts.Token));
Console.WriteLine("Successfully finished");
}
private static async Task GeneralDoJobAndCatchException(Func<Task> func)
{
try
{
await func();
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Console.WriteLine("Do error handling");
}
}
private static async Task DoJob(CancellationToken ct)
{
await Task.Delay(1000, ct);
}
}
We're having the exact same problem. Mainly there is a while-loop that checks for the CancellationToken but you've to catch this exception.
We created the following extension method:
public static async Task<TaskStatus> HideCancellationException(this Task task)
{
try
{
await task;
return task.Status;
}
catch (OperationCanceledException)
{
return TaskStatus.Canceled;
}
}
Having this extension method allows to change this code:
while (!cancellationToken.IsCancellationRequested)
{
// do stuff here...
try
{
await Task.Delay(..., cancellationToken);
}
catch (OperationCanceledException)
{
// expected
}
}
to something like that:
while (!cancellationToken.IsCancellationRequested)
{
// Do stuff here.
await Task.Delay(..., cancellationToken).HideCancellationException();
}
Keep in mind that there is explicitly no overload for Task<T> because the return value in case of cancellation is default. You can't distinguish between default as normal task result and default as result of cancellation. In that case it's better to catch the exception.
You could get rid of the try-catch block altogether by awaiting indirectly with Task.WhenAny, and then querying the status of the completed task:
private static async Task DoJob(CancellationToken ct)
{
var completedTask = await Task.WhenAny(Task.Delay(1000, ct));
if (completedTask.IsFaulted)
{
Console.WriteLine("Error: " + completedTask.Exception.InnerException);
}
else if (completedTask.IsCanceled)
{
// Do nothing
}
else // Success
{
// Do nothing
}
}

Handling exception in task

I'm new to TPL.
I need to handle exception when the SendEmailAlert() method throws any error.Is the following code correct please?
public Task MyMethod()
{
DoSomething();
try
{
string emailBody = "TestBody";
string emailSubject = "TestSubject";
Task.Run(()=> SendEmailAlert(arrEmailInfo));
}
catch (AggregateException ex)
{
ex.Handle((e) =>
{
log.Error("Error occured while sending email...", e);
return true;
}
);
}
}
private void SendEmailAlert(string[] arrEmailInfo)
{
MyClassX.SendAlert(arrEmailnfo[0], arrEmailnfo[1]);
}
I forced an error from within SendEmailAlert() method.But the exception is not getting caught. Could someone advise?
Thanks.
Your Task.Run runs in a different context (you would need a try/catch inside it; or check if the task is done). You could change to use async/await.
Example:
public async void MyMethod()
{
try
{
await ExceptionMethod();
}
catch (Exception ex)
{
// got it
}
}
public async Task ExceptionMethod()
{
throw new Exception();
}

Categories