Введение
Я работаю со сложной внешней библиотекой, где я пытаюсь выполнить ее функции в большом списке элементов. Библиотека не предоставляет хорошего асинхронного интерфейса, поэтому я застрял в некотором старомодном коде.
Моя цель состоит в том, чтобы оптимизировать время, необходимое для выполнения пакета обработки, и продемонстрировать проблему, не добавляя фактическую стороннюю библиотеку. Я создал приближение к проблеме ниже
Проблема
Учитывая не асинхронное действие, где вы можете заранее знать «размер» (то есть, сложность) действия:
public interface IAction
{
int Size { get; }
void Execute();
}
И учитывая, есть 3 варианта этого действия:
public class LongAction : IAction
{
public int Size => 10000;
public void Execute()
{
Thread.Sleep(10000);
}
}
public class MediumAction : IAction
{
public int Size => 1000;
public void Execute()
{
Thread.Sleep(1000);
}
}
public class ShortAction : IAction
{
public int Size => 100;
public void Execute()
{
Thread.Sleep(100);
}
}
Как можно оптимизировать длинный список этих действий, чтобы при параллельном запуске весь пакет выполнялся максимально быстро?
Наивно, вы могли бы просто бросить весь лот на Parallel.ForEach
, с достаточно высоким параллелизмом, и это, безусловно, работает - но должен быть способ оптимально запланировать их, чтобы некоторые из самых больших запускались первыми.
Для дальнейшей иллюстрации проблемы, если мы возьмем супер упрощенный пример
- 1 задание размером 10
- 5 заданий размером 2
- 10 заданий размером 1
И 2 доступных темы. Я мог бы предложить 2 (из многих) способов планирования этих задач (черная полоса - время простоя - ничего не планировать):
Очевидно, что первый завершается раньше, чем второй.
Минимальный полный и проверяемый код
Весь тестовый код, если кому-то нравится bash (попробуйте сделать его быстрее, чем моя наивная реализация ниже):
class Program
{
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
Console.ReadLine();
}
static async Task MainAsync()
{
var list = new List<IAction>();
for (var i = 0; i < 200; i++) list.Add(new LongAction());
for (var i = 0; i < 200; i++) list.Add(new MediumAction());
for (var i = 0; i < 200; i++) list.Add(new ShortAction());
var swSync = Stopwatch.StartNew();
Parallel.ForEach(list, new ParallelOptions { MaxDegreeOfParallelism = 20 }, action =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss}: Starting action {action.GetType().Name} on thread {Thread.CurrentThread.ManagedThreadId}");
var sw = Stopwatch.StartNew();
action.Execute();
sw.Stop();
Console.WriteLine($"{DateTime.Now:HH:mm:ss}: Finished action {action.GetType().Name} in {sw.ElapsedMilliseconds}ms on thread {Thread.CurrentThread.ManagedThreadId}");
});
swSync.Stop();
Console.WriteLine($"Done in {swSync.ElapsedMilliseconds}ms");
}
}
public interface IAction
{
int Size { get; }
void Execute();
}
public class LongAction : IAction
{
public int Size => 10000;
public void Execute()
{
Thread.Sleep(10000);
}
}
public class MediumAction : IAction
{
public int Size => 1000;
public void Execute()
{
Thread.Sleep(1000);
}
}
public class ShortAction : IAction
{
public int Size => 100;
public void Execute()
{
Thread.Sleep(100);
}
}