Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Migrating to Async: Repository

I have a large codebase using my repositories that all implement IRespository and I'm implementing async versions of the methods:

T Find(id);
Task<T> FindAsync(id);
...etc...

There are several kinds of repository. The simplest is based on an immutable collection where the universe of entities is small enough to merit loading them all at once from the DB. This load happens the first time anyone calls any of the IRepository methods. Find(4), for example, will trigger the load if it hasn't happened already.

I've implemented this with Lazy < T >. Very handy and has been working for years.

I can't go cold-turkey on Async so I have to add Async alongside the sync versions. My problem is, I don't know which will be called first - a sync or async method on the repository.

I don't know how to declare my Lazy - if I do it as I've always done it,

Lazy<MyCollection<T>> 

then loading it won't be async when FindAsync() is called first. On the other hand, if I go

Lazy<Task<MyCollection<T>>>

This would be great for FindAsync() but how will a synchronous method trigger the initial load w/o running afoul of Mr. Cleary's warnings about deadlock from calling Task.Result?

Thank you for your time!

like image 871
n8wrl Avatar asked Jun 06 '26 15:06

n8wrl


1 Answers

The problem with Lazy<T> is that there's only one factory method. What you really want is a synchronous factory method if the first call is synchronous, and an asynchronous factory method if the first call is asynchronous. Lazy<T> won't do that for you, and AFAIK there's nothing else built-in that offers these semantics either.

You can, however, build one yourself:

public sealed class SyncAsyncLazy<T>
{
  private readonly object _mutex = new object();
  private readonly Func<T> _syncFunc;
  private readonly Func<Task<T>> _asyncFunc;
  private Task<T> _task;

  public SyncAsyncLazy(Func<T> syncFunc, Func<Task<T>> asyncFunc)
  {
    _syncFunc = syncFunc;
    _asyncFunc = asyncFunc;
  }

  public T Get()
  {
    return GetAsync(true).GetAwaiter().GetResult();
  }

  public Task<T> GetAsync()
  {
    return GetAsync(false);
  }

  private Task<T> GetAsync(bool sync)
  {
    lock (_mutex)
    {
      if (_task == null)
        _task = DoGetAsync(sync);
      return _task;
    }
  }

  private async Task<T> DoGetAsync(bool sync)
  {
    return sync ? _syncFunc() : await _asyncFunc().ConfigureAwait(false);
  }
}

Or you can just use this pattern without encapsulating it:

private readonly object _mutex = new object();
private Task<MyCollection<T>> _collectionTask;

private Task<MyCollection<T>> LoadCollectionAsync(bool sync)
{
  lock (_mutex)
  {
    if (_collectionTask == null)
      _collectionTask = DoLoadCollectionAsync(sync);
    return _collectionTask;
  }
}

private async Task<MyCollection<T>> DoLoadCollectionAsync(bool sync)
{
  if (sync)
    return LoadCollectionSynchronously();
  else
    return await LoadCollectionAsynchronously();
}

The "bool sync" pattern is one Stephen Toub showed me recently. AFAIK there's no blogs or anything about it yet.

like image 114
Stephen Cleary Avatar answered Jun 08 '26 04:06

Stephen Cleary



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!