0

im refactoring out some try catch blocks with a ExcecuteWithLogging method, but exception that occurs in restHandler.GetResultAsync() never get caught, why?

    public async Task<Aaa> SearchByPersonnummerAsync(string personnummer)
    {
        var restHandler = new RestHandler();

        GenericResult<Aaa> aaa= await ExcecuteWithLogging(async () =>
        {
            var res = await restHandler.GetResultAsync<Aaa>(
                _configurationManager.GetSetting("api"),
                "url");

            return res;
        });

        return aaa.Result;

    }


    private T ExcecuteWithLogging<T>(Func<T> function)
    {
        try
        {
            function();

        }
        catch (Exception ex)
        {
            var message = ex.Message;

            // Log

        }
        return default(T);
    }
nihulus
  • 1,475
  • 4
  • 24
  • 46
  • It will only get caught of the asynchronous function is `await`ed. – Kwinten Jul 13 '18 at 12:27
  • @Kwinten but when I get the returned value from the function isnt it is awaited already? – nihulus Jul 13 '18 at 12:31
  • @nihulus Where is the `await` call that is awaiting it? – Servy Jul 23 '18 at 13:40
  • @Servy I got a solution and posted down here, have a look :) – nihulus Jul 25 '18 at 07:55
  • @nihulus The solution to the fact that you're not awaiting your task is not to synchronously wait on it. As you've been told, you need to *await* the operation to asynchronously handle the result. – Servy Jul 25 '18 at 13:13
  • @Servy not sure i got you right, i have await when calling the method, and wait for the task run complete, can you pint point where i do not await? – nihulus Jul 25 '18 at 13:55
  • @nihulus You need to await *every* method that you want to either perform an action after its completion, or observe the results of. You awaited the method in the lambda, and so the lambda is able to run code after the awaited task finishes. The method calling the lambda doesn't await it, and so it doesn't see when it finishes or what its result is. – Servy Jul 25 '18 at 13:58
  • @Servy the method calling you mean ExcecuteWithLogging()? It has a Wait() operation. – nihulus Jul 25 '18 at 14:16
  • @nihulus And you should not be synchronously waiting. The operation is designed to be asynchronous. Creating a method that *claims* to be asynchronous, both in returning a `Task` and having `Async` in the name, but which actually does its work synchronously, is *very* bad. – Servy Jul 25 '18 at 14:21
  • @Servy man just post your answer and please show us how things should be done.. it seems you already know it, would you mind to share it? Thanks in advance! =) – taquion Jul 25 '18 at 16:08

3 Answers3

0

Here is my solution which is working as i prefered:

var aaa = null;
await ExcecuteWithLogging(async () => 
                aaa = await Method());


 public async Task<string> ExcecuteWithLogging(Func<Task> action)
 {
        try
        {
            await action();
        }
        catch (Exception ex)
        {
            // handle exception

            return null;
        }

        return "ok";
 }
nihulus
  • 1,475
  • 4
  • 24
  • 46
  • 1
    You are almost there! What @Servy means with not waiting synchronously is that instead of calling `Wait()` just write `await action()` since action is already a `Func`. This [answer](https://stackoverflow.com/a/13140963/4430204) by Stephen Cleary will help you to understand this – taquion Jul 25 '18 at 15:55
  • Btw this solution was the one I was proposing... to write the overload you are writing but with await instead of Wait. It seems though that @Servy may know another way to do this without the overload accepting a `Func` and having just `Func` as parameter, but that is something you should ask him... – taquion Jul 25 '18 at 15:58
  • @taquion Great explaination from your link, basically I should use async from begin to end. – nihulus Jul 26 '18 at 07:31
  • 1
    And do not call Task.Run.. you can do simply `await action()` since action is already a `Func`. =) – taquion Jul 26 '18 at 18:54
0

I had a similar problem and here is my solution. In my case I couldn't be sure about the functions type, so I simply created an overload for async ones:

    public static T CallAndLogErrors<T, TLog>(Func<T> method, ILogger<TLog> logger)
    {
        try
        {
            return method();
        }
        catch (Exception e)
        {
            LogError(e, logger);
            throw;
        }
    }

    public static async Task<T> CallAndLogErrors<T, TLog>(Func<Task<T>> method, ILogger<TLog> logger)
    {
        try
        {
            return await method();
        }
        catch (Exception e)
        {
            LogError(e, logger);
            throw;
        }
    }

This makes it possible to call methods with T, Task<T> or async Task<T> return types like:

    var result = CallAndLogErrors(() => Method(), logger);
    var result = await CallAndLogErrors(() => Method(), logger);

And all exceptions are catched.

Sven.L
  • 45
  • 1
  • 9
-1

The problem is that you are not implementing an overload of ExecuteWithLogging that resolves specifically for Func<Task<T>>. Remember that lambda expressions can be converted into any compatible delegate, that is why your async lambda is assigned succesfully to your current ExecuteWithLogging method.

Before you read the rest of the answer pleas have a look to the following article, it helped me a lot when I came across with exactly the same problem that you are facing now. I am pretty much sure that after reading that article you will understand what is happening here, but for completness sake here you have some samples that will make things clarer. I have modified your methodos in the following way:

public static async void SearchByPersonnummerAsync(string personnummer)
{
    var aaa = await ExcecuteWithLogging(async () =>
    {
        Console.WriteLine("Executing function");
        var res = await Task.Run(() =>
        {
            Thread.Sleep(100);
            Console.WriteLine("Before crashing");
            throw new Exception();
            return 1;
        });

        Console.WriteLine("Finishing execution");
        return res;
    });

}

private static T ExcecuteWithLogging<T>(Func<T> function)
{
    try
    {
        Console.WriteLine("Before calling function");
        function();
        Console.WriteLine("After calling function");
    }
    catch (Exception ex)
    {
        var message = ex.Message;
        Console.WriteLine(message);
    }

    Console.WriteLine("Returning..");
    return default(T);
}

This is just where you are now, I only added some console logging code, what is the output?

enter image description here

As you can see ExecutingWithLogging is returning when the async delegate has not even crashed!

Let's add an overload that accepts a Func<Task<T>>

private static async Task<T> ExcecuteWithLogging<T>(Func<Task<T>> function)
{
    T result;
    try
    {
        Console.WriteLine("Before calling function");
        result =  await function();
        Console.WriteLine("After calling function");

    }
    catch (Exception ex)
    {
        var message = ex.Message;
        Console.WriteLine(message);
        return default(T);

    }

    Console.WriteLine("Returning..");
    return result;
}

The compiler will pick this now, from the article mentioned above:

The compiler prefers the method call that expects a delegate that returns a Task. That’s good, because it’s the one you’d rather use. The compiler comes to this conclusion using the type inference rules about the return value of anonymous delegates. The “inferred return type” of any async anonymous function is assumed to be a Task. Knowing that the anonymous function represented by the lambda returns a Task, the overload of Task.Run() that has the Func argument is the better match.

Now the output is:

enter image description here

And you get your exeption catched. So what is the moral? Again, I quote the mentioned article:

First, avoid using async lambdas as arguments to methods that expect Action and don’t provide an overload that expects a Func. If you do that, you’ll create an async void lambda. The compiler will happily assume that’s what you want.

Second, if you author methods that take delegates as arguments, consider whether programmers may wish to use an async lambda as that argument. If so, create an overload that uses Func as the argument in addition to Action. As a corollary, create an overload that takes Func> in addition to Task for arguments that return a value.

EDIT As @Servy points out in the comments if you mantain only the original version of ExecuteWithLogging T is actually a Task<Aaa>... but the only way I can think of awaiting without the overload is:

private static async Task<T> ExcecuteWithLogging<T>(Func<T> function)
{
    try
    {

        var result =  function();
        if (result is Task t)
        {
            return await t;
        }

        return result;
    }
    catch (Exception ex)
    {
        var message = ex.Message;
        Console.WriteLine(message);
    }

    Console.WriteLine("Returning..");
    return default(T);
}

But besides being ugly IMHO, it always fails since the call to function() runs synchronously and by the time you want to await the task has already ended or crashed. But the worst part of this approach is that it does not even compile, the compiler complains with: Can not implicitly convert type void to T

taquion
  • 2,667
  • 2
  • 18
  • 29
  • The lambda isn't being converted to an `Action`. It's being converted to a `Func>`. It's just that nothing is observing the result of the task that is actually returned. – Servy Jul 23 '18 at 13:39
  • @Servy I did not say it was being converted to an Action, the article is just a reference. Anyway, if you do not add the overloaded method you can not observe the result since T is not a `Func>`, try to await `function()`.. it does not compile.. – taquion Jul 23 '18 at 13:57
  • But `T` *is* a `Task`, meaning that the method *is* accepting a `Func>`. It's just not being awaited. – Servy Jul 23 '18 at 14:01
  • Oh, and your answer *does* say that the delegate isn't resolving to `Func>`. It's literally your opening sentence. That's wrong, it *does* resolve to exactly that. – Servy Jul 23 '18 at 14:06
  • @Servy thanks for pointing that out, I wanted to mean that the OP should write a method that resolves specifically to `Func>`, I will edit my answer adding that adverb. I know T is actually `Task`... how would you await it without an overload? – taquion Jul 23 '18 at 14:14
  • @Servy _It's just not being awaited_ ...please show how to just await `Func` for the scenario where `T` is `Task`, this way I can modify my answer accordingly, or better yet, add yours so that I can delete mine. I mean, it is just about awaiting.. – taquion Jul 23 '18 at 15:17