并行执行多个任务异步并将结果保存到数据库UnitOfWork为null

时间:2014-08-30 12:43:52

标签: c# parallel-processing task-parallel-library async-await

我最近发布了另一个关于如何处理数据库中的项目列表的问题,如果流程失败,则重试3次。

问题可以在这里找到:C# process large list of items in a loop and retry if fails

我对收到的问题做了一些修改,这是代码:

我有一个继承ApiController类的抽象类,我的所有Web Api控制器都继承ApiBaseController

ApiBaseController中,我定义了使用存储库模式的UnitOfWork,以便CRUD SQL数据库。 UnitOfWork和存储库模式工作正常,我已经测试过它,我没有遇到任何问题。

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

这里我有一个JobController,它继承了ApiBaseController,它继承了ApiController,使其成为Web Api控制器。

此控制器有一个端点api/endpoint/sendrequests,调用它时将从数据库中获取所有Jobs,并以10个批次处理它们。

方法ProcessTaskAsync将处理从数据库中检索到的每个单独的任务,如果它失败,它将再次尝试处理它2次,直到该任务被忽略。

除了在处理任务后的ProcessTaskAsync中我尝试使用UnitOfWork await Uow.Results.AddAsync(result);将任务结果保存到数据库之外,一切都运行良好。在这里它失败了!问题是Uow对象为空,我不明白为什么。我的想法是因为任务是异步处理的,控制器执行结束意味着控制器被处理掉了所以UnitOfWork。

知道为什么Uow为空,我该如何解决?

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

这用于将List作为List返回页面返回10.这位于静态类中。

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

1 个答案:

答案 0 :(得分:5)

private async void ProcessTaskAsync(Job item)

实际工作方法必须返回您可以等待的Taskasync void可能仅适用于顶级UI事件,绝对不是值得期待的工作。

您使用Task.Factory.StartNew生成此类工作并等待结果。但是你正在等待的只是外部任务(并且没有内部任务,它是void)开始工作。我认为你的控制器在工作方法使用你的UoW实例之前就已经处理好了,因为你实际上并没有在Controller方法中等待这项工作。

你应该做的是摆脱Task.Factory.StartNew(用于并行工作,而不是异步),并在实际工作时使ProcessTaskAsync返回Task信号。完成。然后,您可以使用Task等待那些WhenAll,它应该按预期工作。


可能是async相关内容的最佳参考/资源是Stephen Cleary的博客。我强烈建议您阅读TPL文章http://blog.stephencleary.com/