Home > database >  Calling IAsyncEnumerable inside constructor
Calling IAsyncEnumerable inside constructor

Time:09-27

I have the code below, that is causing me a DeadLock. In the code, i need to call a Database to fill a List inside a constructor. The database call returns an IAsyncEnumerable.

public class DatabaseRegistries
{
  public List<people> List {get;}

  public DatabaseRegistries()
  {
    List = DataBaseCallReturnIEnumerableAsync().ToListAsync().GetAwaiter().GetResult();
  }

}

Any Clue on how to do it without causing a Dead Lock ?

Thanks,

Glayson

CodePudding user response:

  • async methods represent an IO boundary, and constructors should not be performing any IO, so you need to rethink/redesign your class.
  • One option is to use a static factory method (which would be an async method) which loads the list and then passes thast loaded list to the ctor.

Like so:

public class DatabaseRegistries
{
    public static async Task<DatabaseRegistries> LoadAsync( SomeDataSource data )
    {
        if( data is null ) throw new ArgumentNullException(nameof(data));
        
        List<Person> list = await data.GetPeopleToListAsync();
        return new DatabaseRegistries( list );
    }

    private DatabaseRegistries( IReadOnlyList<Person> list )
    {
        this.List = list ?? throw new ArgumentNullException(nameof(list));
    }

    public IReadOnlyList<Person> List { get; }
}

CodePudding user response:

[UPDATED] - To return List instead of IAsyncEnumerable

As mentioned by other in the comments, your code needs to be refactored to move the call out of the constructor and fix the deadlock. The following are hypothetical example implementations based upon your code return IAsyncEnumerable.

The "Cached" example method utilizes SpinWait as opposed to using a traditional lock, which substantially improves performance. You may want to consider making the following variables static if your use case requires state to be maintained across multiple instantiations (i.e. new-ing multiple instances) of the class. For example, if you're using the class with dependency injection as a Scoped or Transient dependency as opposed to Singleton, you would need to add the static keyword as follows to maintain state:

private static List<people> _list1 = new();
private static volatile int _interLock1 = 0;

Please note that the following examples have not been compiled or tested. Should you run into any issues that require assistance - let me know.

Examples Based on Your Code Returning IAsyncEnumerable

Requires reference to Nuget package System.Linq.Async.

public class DatabaseRegistries
{
    private MyDbContext _context; //ctor injected database context
    public DatabaseRegistries(MyDbContext context) =>_context = context;

    // NO CACHE EXAMPLE
    public async Task<List<people>> GetPeople(CancellationToken token = default)
    {
        // Example returning List from IAsyncEnumerable w/o caching
        return await context.peopleAsyncEnumerable().ToListAsync(token);
    }
    
    // CACHE EXAMPLE
    private List<people> _list1 = new();
    private volatile int _interLock1 = 0; //need separate int variable for each cache list
    public async Task<List<people>> GetPeople_Cached(CancellationToken token = default)
    {
        if (_list1.Count == 0)
        {
            // acquire exclusive lock
            for (SpinWait sw = new SpinWait(); Interlocked.CompareExchange(ref _interLock1, 1, 0) == 1; sw.SpinOnce()) ;

            // populate cache if empty
            if (_list1.Count == 0) _list1.AddRange(await _context.peopleAsyncEnumerable().ToListAsync(token));

            // release exclusive clock
            _interLock1 = 0;
        }
        
        return _list1;
    }
}

CodePudding user response:

You could initialize the task in the constructor, then just await for it when you are in the proper context. For example:

public class DatabaseRegistries
{
  public Task<List<people>> List { get; }

  public DatabaseRegistries()
  {
    List = DataBaseCallReturnIEnumerableAsync().ToListAsync();
  }
}

You'd then call it as so:

var myDbStuff = new DatabaseRegistries();
var peopleList = await myDbStuff.List;
  • Related