0

I am wondering if it would be appropriate to use the TaskCompletionSource class in the following example:

Original Code / Without TaskCompletionSource

public async Task GetData() 
{
    if (!IsLoaded) 
    {
        MyList = await Mediator.Send(new GetData());
        IsLoaded = true;
    }
}

This function is available as part of a singleton scoped service within my .NET Core Web application. It is possible for multiple objects to use the records in MyList and will await the GetData() function before using the list.

You can see from the sample function, that it is possible for the method to be called multiple times while the first call is still awaiting the result from the Mediator request. I want a solution to allow multiple objects to await the same Task. So is it appropriate to use a TaskCompletionSource object for this purpose?

Refactored Using TaskCompletionSource

private TaskCompletionSource getDataTask;

public async Task VerifyDataIsLoaded() 
{
    if (getDataTask == null) 
    {
        getDataTask = new TaskCompletionSource();
        Task.Factory.StartNew(async () => 
        {
            await GetData();
            getDataTask.TrySetResult();
        }
    }
    return getDataTask.Task;
}

private async Task GetData() 
{
    MyList = await Mediator.Send(new GetData());
}

This should result in the first call to VerifyDataIsLoaded() instantiating an instance of the TaskCompletionSource getDataTask object, kicking off the GetData() function in a new threaded Task, and returning the getDataTask.Task property.

Now if any other objects call into VerifyDataIsLoaded() while the GetData() function is awaiting the return from the Mediator object, they will all await the same Task, and prevent any redundant calls to GetData.

Thoughts?? ... bad idea?? better way??

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Thrifty2100
  • 105
  • 1
  • 8
  • I think you just missed one very important aspect: What if `GetData` fails? Because you are calling in a fire and forget fashion that's why if it fails you will never know and the `getDataTask` will never complete. – Peter Csala Jul 21 '21 at 07:16
  • Probably this is what you want: [Enforce an async method to be called once](https://stackoverflow.com/questions/28340177/enforce-an-async-method-to-be-called-once/) – Theodor Zoulias Jul 21 '21 at 10:30

1 Answers1

2

The normal pattern for this is Lazy<Task<T>>, or a custom AsyncLazy<T> type.

private readonly Lazy<Task> getDataTask = new(GetData);
public Task VerifyDataIsLoadedAsync() => getDataTask.Value;

Though in general, I don't recommend this kind of async initialization API because it's on consumers to always remember to initialize it. I prefer an asynchronous factory pattern instead.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810