0

I recently posted another question about how to process a list of items from a database and retry 3 times if the process fails.

The question can be found here: C# process large list of items in a loop and retry if fails

I made some changes to the answer I received to that question and here is the code:

I have an abstract class which inherits ApiController class and all my Web Api controllers inherit the ApiBaseController:

In the ApiBaseController I have defined the UnitOfWork that uses Repository pattern in order to CRUD the SQL db. The UnitOfWork and repository pattern works fine, I have tested it and I have no problems with it.

public abstract class ApiBaseController : ApiController
{
    protected UnitOfWork Uow { get; set; }          

    protected override void Dispose(bool disposing)
    {
        if (Uow != null && Uow is IDisposable)
        {
            ((IDisposable)Uow).Dispose();
            Uow = null;
        }
        base.Dispose(disposing);
    }
}

Here I have a JobController which inherits the ApiBaseController which inherits the ApiController that makes it a Web Api Controller.

This controller has one endpoint api/endpoint/sendrequests which when is called will get all the Jobs from the db and process them in batches of 10.

The method ProcessTaskAsync will process every individual task retrieved from database and if it fails then it will try to process it 2 more times until the task will be ignored.

Everything works fine and nice except that in the ProcessTaskAsync after the task is processed I try to save the result of the task to the database using the UnitOfWork await Uow.Results.AddAsync(result);. Here it fails! The problem is the Uow object is null and I don't get it why. I am thinking is because the tasks are processed asynchronously the controller execution ends that means the controller is disposed and so the UnitOfWork.

Any idea why the Uow is null and how can I solve this?

[AllowAnonymous]
[RoutePrefix("api/endpoint")]
public class JobController : ApiBaseController
{

private const int PAGED_LIST_SIZE = 10;

private const int MAX_RETRY_COUNT = 3;

public JobController()
{
    Uow = new UnitOfWork();          
}       

[HttpPost]
[AllowAnonymous]
[Route("sendrequests")]
public async Task<IHttpActionResult> SendRequests()
{
    try
    {              

        var items = await Uow.Jobs.GetAllAsync();

        await ProcessTasks(items);                

        return Ok();

    }
    catch (Exception ex)
    {
        return BadRequest();
    }
}


private async Task ProcessTasks(List<Job> items)
{
    var pagedResults = items.Paged<Job>(PAGED_LIST_SIZE);

    if (pagedResults != null && pagedResults.Count() > 0)
    {
        foreach (var collection in pagedResults)
        {
            List<Task> tasks = new List<Task>();

            foreach (var item in collection.ToList())
            {
                var task = Task.Factory.StartNew(() => ProcessTaskAsync(item));
                tasks.Add(task);
            }

            try
            {
                await Task.WhenAll(tasks.ToArray());                   
            }
            catch (Exception ez)
            {
                //log here the failed ones
            }
        }
    }

}

private async void ProcessTaskAsync(Job item)
{
    if (item.Processed >= MAX_RETRY_COUNT)
    {
        Debug.WriteLine(string.Format("Max count {0} reached for task: {1} ", item.Processed.ToString(), item.Name));
        return;
    }

    item.Processed++;

    try
    {
        Result result = await item.Process();

        if (result != null)
        {
           await Uow.Results.AddAsync(result);
        }

        Debug.WriteLine(string.Format("working item name: {0}", item.Name));
    }  
    catch (Exception ex)
    {
        Debug.WriteLine(string.Format("Exception occured: {0}", ex.Message.ToString()));            
        ProcessTaskAsync(item);
    }
}

This is used in order to return a List as List paged by 10. This is located into a static class.

public static IEnumerable<IEnumerable<T>> Paged<T>(this IEnumerable<T> enumerable, int chunkSize = 10)
{
    int itemsReturned = 0;
    var list = enumerable.ToList();
    while (itemsReturned < list.Count)
    {
        int currentChunkSize = Math.Min(chunkSize, list.Count - itemsReturned);
        yield return list.GetRange(itemsReturned, currentChunkSize);
        itemsReturned += currentChunkSize;
    }
}
Community
  • 1
  • 1
David Dury
  • 5,537
  • 12
  • 56
  • 94

1 Answers1

5
private async void ProcessTaskAsync(Job item)

The actual work method has to return Task that you can await on. async void is good probably only for things like top-level UI events, definitely not awaitable work.

You spawn work like this with Task.Factory.StartNew and await the results. But what you're awaiting is only the outer task (and there's no inner task, it's void) which starts the work. I think you're right that your Controller gets disposed before the worker methods get to use your UoW instance - because you're not actually awaiting that work in the Controller method.

What you should do is get rid of the Task.Factory.StartNew (which is used for parallel work, not asynchronous), and make the ProcessTaskAsync return a Task signalling when the actual work is done. Then you can await those Tasks with the WhenAll instead, and it should work as intended.


Possibly the best reference/resource for async-related stuff is Stephen Cleary's blog. I highly recommend reading through the TPL articles http://blog.stephencleary.com/

Honza Brestan
  • 10,637
  • 2
  • 32
  • 43