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?

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:

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