正在构造函数内部调用IAsyncEnumerable



我有下面的代码,这导致了我的死锁。在代码中,我需要调用一个数据库来填充构造函数中的列表。数据库调用返回一个IAsyncEnumerable。

public class DatabaseRegistries
{
public List<people> List {get;}
public DatabaseRegistries()
{
List = DataBaseCallReturnIEnumerableAsync().ToListAsync().GetAwaiter().GetResult();
}
}

关于如何在不造成死锁的情况下做到这一点,有什么线索吗?

谢谢,

Glayson

  • async方法表示IO边界,构造函数不应执行任何IO,因此需要重新思考/重新设计类
  • 一种选择是使用静态工厂方法(这将是一种异步方法(,该方法加载列表,然后将最后加载的列表传递给ctor

如此:

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; }
}

[UPDATED]-返回List而不是IAsyncEnumerable

正如其他人在评论中提到的,您的代码需要重构,以将调用移出构造函数并修复死锁。以下是基于返回IAsyncEnumerable的代码数据库调用的假设示例实现。正如下面提供的第一个例子所建议的那样,应该将代码重构为异步方法——然而,我在构造函数"之后包含了第二个例子;反模式";在您的问题中的示例代码中,这也应该有效。

";缓存的";示例方法使用SpinWait而不是使用传统的lock,这显著提高了性能。如果您的用例需要在类的多个实例(即new多个实例(之间维护状态,您可能需要考虑将以下变量设为static。例如,如果使用带有依赖项注入的类作为ScopedTransient依赖项,而不是Singleton,则需要添加如下静态关键字来维护状态:

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

请注意,以下示例尚未编译或测试。如果您遇到任何需要帮助的问题,请告诉我

使用异步方法而不是属性/Ctor反模式的示例

需要引用Nuget包System.Linq.Anc.

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;
}
}

不带死锁的替代Ctor反模式

虽然实现GetPeople_Cached()是一种反模式,但它可以在构造函数中用于加载List,而不会导致死锁。我没有办法进行测试,但我很有信心它不会导致死锁。

public class DatabaseRegistries
{
private MyDbContext _context; //ctor injected database context
public DatabaseRegistries(MyDbContext context) 
{ 
_context = context;
GetPeople_Cached().ConfigureAwait(false).GetAwaiter().GetResult();
}
// List
public IReadOnlyList<people> List => _list;
// change to non-static for most up-to-date results per instatiation
private static List<people> _list = new();
// CACHE EXAMPLE
private static volatile int _interLock1 = 0; //need separate int variable for each cache list
private async Task GetPeople_Cached(CancellationToken token = default)
{
if (_list.Count == 0)
{
// acquire exclusive lock
for (SpinWait sw = new SpinWait(); Interlocked.CompareExchange(ref _interLock1, 1, 0) == 1; sw.SpinOnce()) ;
// populate cache if empty
if (_list.Count == 0) _list.AddRange(await _context.peopleAsyncEnumerable().ToListAsync(token));
// release exclusive clock
_interLock1 = 0;
}
}
}

最新更新