Jorge
Jorge

Reputation: 79

How do I call an async method from inside a static class?

So I am using Redis cache (Stack Exchange Redis) in my application, and I have a static class that creates the connection. I want to be able to call an async method somewhere inside this static class but I am receiving an error "The await operator can only be used within an async lambda expression. Consider making this lambda expression with async modifer.

My static class is below.

public static class RedisConnection {
   private static ConnectionMultiplexer connMulti = null;
   private static Lazy<ConnectionMultiplexer> lazyConn = new Lazy<ConnectionMultiplexer>(() => {
         string redisConnString = "";  // Need to replace this with call to async method
         connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnString);
         return connectionMultiplexer;
       });

       public static ConnectionMultiplexer Conn => lazyConn.Value;
}

Basically where my comment is, is where I want to replace that variable with a call to an async method that fetches the conn string. But the because how Stack Exchange Redis is set up, the ConnectionMultiplexer needs to be sync, so I need to keep the class static, the Lazy connection static and the lambda func sync. Please see below

public static class RedisConnection {
   private static ConnectionMultiplexer connMulti = null;
   private static Lazy<ConnectionMultiplexer> lazyConn = new Lazy<ConnectionMultiplexer>(() => {
         var redisConnString = await getKey();  // Error
         connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnString);
         return connectionMultiplexer;
       });

       public static ConnectionMultiplexer Conn => lazyConn.Value;
}

What can I do?

Upvotes: 0

Views: 829

Answers (1)

Michał Turczyn
Michał Turczyn

Reputation: 37480

What you could do is to expose ValueTask<ConnectionMultiplexer> instead of ConnectionMultiplexer, like below

public static class RedisConnection
{
    private static Lazy<ValueTask<ConnectionMultiplexer>> lazyConn =
        new Lazy<ValueTask<ConnectionMultiplexer>>(
            async () => 
            {
                Console.WriteLine("Establishing connection asynchronously");
                var redisConnString = await GetKeyAsync();  
                var connectionMultiplexer = ConnectionMultiplexer.Connect(redisConnString);
                return connectionMultiplexer;
            });

    public static ValueTask<ConnectionMultiplexer> Conn => lazyConn.Value;

    private static Task<string> GetKeyAsync()
    {
        return Task.FromResult("asdf");
    }
}

Then the usage would be the following:

public class ExampleService
{
    public async Task GetSomeDataFromRedisAsync()
    {
        var connection = await RedisConnection.Conn;
     
        connection = await RedisConnection.Conn;
    }
}

So the using code would be responsible for awaiting the connection. And potentially we would be calling multiple times this getter, that's why we used ValueTask as it will be most of the times already completed by the time it will be accessed.

I added Console.WriteLine and double establishing connection to prove that if it would be called two times, it would try to connect once - other call to get the connection would return completed ValueTask with already connection ready to use.

One more thing here would be to control thread ssafety, but you can easily find resources on google regarding thread safety in async code and thread safety in using Lazy - it even has already built in implementation for that :) (one of the constructor overload for example, accepts threadSafe flag)

Upvotes: 1

Related Questions