Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I overload function to accept both async and synchronized version of callback parameter

public static T SyncVer<T>(Func<T> callback)
{
    using (new LogContext("new logging context"))
    {
        try
        {
            return callback();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);

            throw;
        }
    }
}

public static async Task<T> AsyncVer<T>(Func<Task<T>> callback)
{
    using (new LogContext("new logging context"))
    {
        try
        {
            return await callback();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);

            throw;
        }
    }
}

Please consider the code above. You may see most of the code in both functions is the same. I am searching for a way to group them up by overloading them into one so that I don't need to duplicate the content. Is there a way to take out the similar part of both functions?

Any help provided will be appreciated. Thanks in advance.

like image 320
mannok Avatar asked Oct 25 '25 05:10

mannok


1 Answers

Note that Task.FromResult will allocate, and GetAwaiter().GetResult() may dead lock

Personally I would just create two methods and not worry about it.

However, another (and slightly safer) approach is to wrap your callbacks in a ValueTask. ValueTasks work best if they execute synchronously, however they have a few subtle limitations and should never be awaited more than once.

The assumption is, this is all about creation an awaitable and non awaitable delegate overload, code reuse, and awaiting the sync version of this call is not a problem for you.

Given

public static async ValueTask<T> SomethingAsync<T>(Func<T> callback)
 =>  await SomethingAsync(() => new ValueTask<T>(callback()));

public static async ValueTask<T> SomethingAsync<T>(Func<Task<T>> callback)
 =>  await SomethingAsync(() => new ValueTask<T>(callback()));

public static async ValueTask<T> SomethingAsync<T>(Func<ValueTask<T>> callback)
{
   using (new LogContext("new logging context"))
   {
      try
      {
         return await callback();
      }
      catch (Exception ex)
      {
         Console.WriteLine(ex);
         throw;
      }
   }
}

Usage

public static string DoSomething()
{
   Console.WriteLine("execute sync");
   return "sync result";
}

public static async Task<string> DoSomethingAsync()
{
   Console.WriteLine("Execute async");
   await Task.Delay(100);
   return "async result";
}


...

Console.WriteLine(await SomethingAsync(DoSomething));
Console.WriteLine(await SomethingAsync(DoSomethingAsync));

Output

Create
execute sync
Dispose
sync result
Create
Execute async
Dispose
async result

To add some more efficiencies you can elide wrappers

Exmaple

public static  ValueTask<T> SomethingAsync<T>(Func<T> callback)
{
   try
   {
      return SomethingAsync(() => new ValueTask<T>(callback()));
   }
   catch (Exception e)
   {
      return ValueTask.FromException<T>(e);
   }
}

public static ValueTask<T> SomethingAsync<T>(Func<Task<T>> callback)
{
   try
   {
      return SomethingAsync(() => new ValueTask<T>(callback()));
   }
   catch (Exception e)
   {
      return ValueTask.FromException<T>(e);
   }
}

Note : ValueTask.FromException is only available for .NET 5.0+

The benefits of this approach :

  • Less allocations for the sync version of this method.
  • There is no chance of a deadlock
  • It doesn't block on async method
  • It gives you a Value Task overload

The downsides are

  • You need to wrap the async task in a ValueTask, though it's only a stack allocation
  • You will need to create two overloads per method (in total three signatures)
  • Neither version will can be awaited twice.
  • The sync version is slightly slower as it creates a statemachine

Note : I personally have never had a need to do this, I would just create the two methods ¯\_(ツ)_/¯.


Additional resources

  • Don't Block on Async Code

  • Prefer ValueTask to Task, always; and don't await twice

like image 123
TheGeneral Avatar answered Oct 26 '25 19:10

TheGeneral



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!