Does passing a reference into a BackgroundTaskQueue work item cause the calling class to be held in memory?

39 Views Asked by At

I have the standard Queued Background Tasks setup defined in this article.

When using this service to queue items, via QueueBackgroundWorkItemAsync(Task workItem), what is the effect of passing in references from the calling class?

For example:

public class SomeClass
{
    private readonly IBackgroundTaskQueue _backgroundTaskQueue;
    private readonly IHeavyTaskService _heavyTaskService;

    public SomeClass(IBackgroundTaskQueue backgroundTaskQueue, IHeavyTaskService heavyTaskService) 
    {
        _backgroundTaskQueue = backgroundTaskQueue;
        _heavyTaskService = heavyTaskService;
    }

    public async Task DoSomeWork() 
    {
        await _backgroundTaskQueue.QueueBackgroundWorkItemAsync(async (cancellationToken) =>  
        {
            await _heavyTaskService.PerformTask();
        });

        ... Some other work ...
    }
}
  1. Does this hold a reference to the SomeClass instance open till the work item is completed?
  2. Is this an issue beyond holding memory?
  3. Would this be an issue if the SomeClass instance is large (memory wise)?
  4. Would this also hold the instance that instantiated the SomeClass instance open (even if instantiated via DI)?
  5. Could theoretically the instance be garbage collected before the task completes causing an error?

By "open" I mean prevent clean up of the memory.

Finally, is there a solution to not passing in a reference, say using the service provider? So that the BackgroundTaskQueue can create the IHeavyTaskService instead when the workItem is being processed?

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, ValueTask>> _queue;

    public async ValueTask QueueBackgroundWorkItemAsync(
        Func<IServiceProvider, CancellationToken, ValueTask> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        await _queue.Writer.WriteAsync(workItem);
    }

    ... Other Methods ...
}

internal sealed class QueuedHostedService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider ;

    public IBackgroundTaskQueue TaskQueue { get; }

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, 
        IServiceProvider serviceProvider)
    {
        TaskQueue = taskQueue;

        _serviceProvider = serviceProvider;
    }

    private async Task BackgroundProcessing(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(stoppingToken);

            try
            {
                await workItem(_serviceProvider, stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred executing {WorkItem}.", nameof(workItem));
            }
        }
    }
    
    ... Other methods ...
}

And then create the work item using that service provider? Does this allow for disposal of the calling class? Should this use the IServiceProviderFactory instead?

public async Task DoSomeWork() 
{
    await _backgroundTaskQueue.QueueBackgroundWorkItemAsync(async (serviceProvider, cancellationToken) =>  
    {
        await serviceProvider.GetRequiredService<IHeavyTaskService>().PerformTask();
    });

    ... Some other work ...
}
0

There are 0 best solutions below