0

I work on a .net framework project MVC and currently I am facing the following issue.

I have an api

[HttpGet]
[ActionName("testme")]
public async Task<bool> TestMe()
{
  await AddNoteAsync("Test");
  return true;
}

And the AddNoteAsync is:

public static async Task AddNoteAsync(string note)
{
  try
  {  var client = new Client()              
    await client.Note.CreateAsync(note);    
  }
  catch (Exception)
  {               
   // ignored
  }
}

This works fine when there is an exception from CreateAsync()

The problem comes when I want discard the AddNoteAsync as following:

[HttpGet]
[ActionName("testme")]
public bool TestMe()
{
  _ = AddNoteAsync("Test");
  return true;
}

This one eventually will return

"System.AggregateException A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread."

even though that I await inside the AddNoteAsync.

How can I modify the code so that I will not get aggregation exception?

Bas H
  • 2,114
  • 10
  • 14
  • 23
dkotsopo
  • 3
  • 4
  • Why did you switch to the discard? What did you *think* it was going to change? And you already know how to modify the code to avoid the error - roll back your change. – Damien_The_Unbeliever Nov 25 '21 at 13:41
  • 3
    This has nothing to do with the discard variable and everything to do with you also removing `await`. This turned it into a fire-and-forget thing. You could do `var task = AddNoteAsync(..` and get the same result. Put the await back. – Lasse V. Karlsen Nov 25 '21 at 13:49
  • @Damien_The_Unbeliever, the problem is that the AddNoteAsync is part of several steps that follow and i dont want to await for it since it just notifies someone. I undestand that the best approach is to await, but I was wondering if there is another way to fire and forget it – dkotsopo Nov 25 '21 at 13:55
  • According to [David Fowler et. al.'s async guidance](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md#timer-callbacks), your code should work as expected. Have you tried using `Task.Run(() => AddNoteAsync("Test"))` instead? – Richard Deeming Nov 25 '21 at 14:00
  • TaskRun did the trick. Task.Run(async() => { await AddNoteAsync("Test"); } Thanks @RichardDeeming – dkotsopo Nov 25 '21 at 14:09
  • 1
    Your service scope will be disposed when your controller action has completed. This might break your fire and forget task. You might want to queue these tasks and process them in a `BackgroundService`. – Jeremy Lakeman Nov 26 '21 at 00:08
  • @dkotsopo this didn't do the trick, it covered up the problem. The exception is still unobserved. And your code is broken to begin with. It can throw if it uses any service that isn't a singleton. In ASP.NET Core requests define a DI scope so once a request completes any instances created will be disposed. Even without DI, any objects created during the request are considered out of scope and will be garbage-collected. Expect `ObjectDisposedException`s to occur regularly – Panagiotis Kanavos Nov 26 '21 at 08:53
  • Your code will also fail when the app pool recycles. When an app pool is recycled the server will wait for a bit for executing requests and threads to complete. Since the server knows nothing about your task, it can't await for it to complete and can't notify it to cancel. It will just shut down your app pool which could result in corrupt data, botched calls and more – Panagiotis Kanavos Nov 26 '21 at 09:00
  • @PanagiotisKanavos I see your point and thank you for the info. Currently I call it like Task.Run (async => await AddNoteAsync("test")).configureAwait(false). and when the addNoteAsync throws an exception, I placed a try catch inside the AddNoteAsync method that handles the error and it does not throw me AggregationException – dkotsopo Nov 26 '21 at 15:35

1 Answers1

1

Using this code:

[HttpGet]
[ActionName("testme")]
public bool TestMe()
{
  _ = AddNoteAsync("Test");
  return true;
}

You are invoking a potentially asynchronous method.

If the method has not completed, TestMe will return immediately and the exception won't be caught.

You can see the difference in these 2 versions of AddNoteAsync:

async Task AddNoteAsync1(string s)
{
    throw new Exception("BOOM!");
}
async Task AddNoteAsync2(string s)
{
    await Task.Delay(10000);
    throw new Exception("BOOM!");
}
``
Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59