Запуск одной и той же задачи несколько раз параллельно с EF Core - PullRequest
0 голосов
/ 30 октября 2019

У меня есть задача, которая генерирует файл PDF для заказа (создание одного PDF занимает около 10 секунд):

public async Task GeneratePDF(Guid Id) {
   var order = await 
      _context
      .Orders
      .Include(order => order.Customer)
      ... //a lot more Include and ThenInclude statements
      .FirstOrDefaultAsync(order ==> order.Id == Id);
   var document = ...  //PDF generated here, takes about 10 seconds
   order.PDF = document ;
   _context.SaveChangesAsync();
}

Я попробовал следующее:

public async Task GenerateAllPDFs() {
   var orderIds = await _context.Orders.Select(order=> order.Id).ToListAsync();
   foreach (var id in orderIds)
   {
      _ = GeneratePDF(id).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
   }
}

это дает мне ошибку:

System.ObjectDisposedException: Невозможно получить доступ к удаленному объекту. Распространенной причиной этой ошибки является удаление контекста, который был разрешен путем внедрения зависимости, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose () для контекста или заключаете контекст в оператор using. Если вы используете внедрение зависимости, вы должны позволить контейнеру ввода зависимости позаботиться об удалении экземпляров контекста.

Если я изменю задачу следующим образом ...

public async Task GenerateAllPDFs() {
   var orderIds = await _context.Orders.Select(order=> order.Id).ToListAsync();
   foreach (var id in orderIds)
   {
      _ = await GeneratePDF(id);
   }
}

. ..это запускает задачу для каждого заказа последовательно, требуя возрастов (у меня несколько тысяч заказов, около 10 секунд на заказ) ...

Как я могу выполнить эту задачу параллельно для всехпорядки в контексте, так что время, необходимое для выполнения, намного меньше, чем последовательная обработка?

Ответы [ 4 ]

2 голосов
/ 30 октября 2019

Вы можете привязать свои идентификаторы заказов к задачам и ждать их всех как:

public async Task GeneratePDF(Order order) {
   var document = ...  //PDF generated here, takes about 10 seconds
   order.PDF = document ;
}

public async Task GenerateAllPDFs() {
   var orderIds = await _context.Orders.ToListAsync();
   var tasks = orderIds.Select((order) => GeneratePDF(order).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted));
   await Task.WhenAll(tasks);
   await _context.SaveChangesAsync();
}
1 голос
/ 30 октября 2019

Вот мое предложение из комментария в качестве ответа. Я бы разделил его на 3 части:

1) получить все заказы,

2) затем сделать Parallel.Foreach для генерации всех документов параллельно. и назначьте каждый документ в правильном порядке и в конце

3) выполните один _context.SaveChangesAsync();, чтобы выполнить массовое обновление данных на сервере

public async Task GenerateAllPDFs()
{
    var allOrders = await _context.Orders.ToListAsync();
    System.Threading.Tasks.Parallel.ForEach(allOrders, order => 
    {
        var document = ...  //PDF generated here, takes about 10 seconds
        order.PDF = document ;
    });
    await _context.SaveChangesAsync();
}
0 голосов
/ 30 октября 2019

Я думаю, что мне придется «продублировать» метод GeneratePDF, чтобы упростить пакетную обработку путем реализации других ответов, поскольку мне нужен этот метод также в не пакетном режиме ...

0 голосов
/ 30 октября 2019

Вам необходимо реализовать параллельное программирование.

https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming

public class Example
{
   public static void Main()
   {
      Task[] taskArray = new Task[10];
      for (int i = 0; i < taskArray.Length; i++) {
         taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
                                                  CustomData data = obj as CustomData;
                                                  if (data == null) 
                                                     return;

                                                  data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
                                               },
                                               new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks} );
      }
      Task.WaitAll(taskArray);     
      foreach (var task in taskArray) {
         var data = task.AsyncState as CustomData;
         if (data != null)
            Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
                              data.Name, data.CreationTime, data.ThreadNum);
      }                     
   }
}
...