Я обнаружил, что объединение Task.Run с plinq чрезвычайно медленно, поэтому я провел простой эксперимент:
int scale = 32;
Enumerable.Range( 0, scale ).AsParallel().ForAll( i => {
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ ) { }
} );
} );
plinq внутри plinq работает хорошо, завершается за 14 миллисекунд
int scale = 32;
Task[] tasks = Enumerable.Range( 0, scale ).Select( i => Task.Run( async () =>
{
Task[] _tasks = Enumerable.Range( 0, scale ).Select( j => Task.Run( () =>
{
for ( int k = 0; k < scale; k++ ) { }
} ) ).ToArray();
await Task.WhenAll( _tasks );
} ) ).ToArray();
await Task.WhenAll( tasks );
Задача внутри задачи также заканчивается за 14 миллисекунд, но если я заменю Task.Run внутри на plinq следующим образом:
int scale = 32;
Task[] tasks = Enumerable.Range( 0, scale ).Select( i => Task.Run( () =>
{
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ ) { }
} );
} ) ).ToArray();
await Task.WhenAll( tasks );
Выполнение займет 29 секунд.Ситуация ухудшается, если scale
переменная больше.
Может кто-нибудь объяснить, что произошло в этом случае?
Редактировать:
Я провел еще один эксперимент:
static async Task Main( string[] args )
{
Stopwatch stopwatch = Stopwatch.StartNew();
int scale = 8;
Task[] tasks = Enumerable.Range( 0, scale ).Select( id => Run( scale, id ) ).ToArray();
await Task.WhenAll( tasks );
Console.WriteLine( $"ElapsedTime={stopwatch.ElapsedMilliseconds}ms" );
}
static Task Run( int scale, int id )
{
return Task.Run( () =>
{
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ )
{
}
Console.WriteLine( $"[{DateTimeOffset.Now.ToUnixTimeMilliseconds()}]Task {id} for loop {j} end" );
} );
} );
}
И вот часть результата:
[1557475215796]Task 0 for loop 6 end
[1557475215796]Task 0 for loop 7 end
[1557475216776]Task 4 for loop 0 end
[1557475216776]Task 4 for loop 1 end
[1557475216777]Task 4 for loop 2 end
[1557475216777]Task 4 for loop 3 end
[1557475216778]Task 4 for loop 4 end
[1557475216778]Task 4 for loop 5 end
[1557475216779]Task 4 for loop 6 end
[1557475216780]Task 4 for loop 7 end
[1557475217774]Task 5 for loop 0 end
[1557475217774]Task 5 for loop 1 end
[1557475217775]Task 5 for loop 2 end
Посмотрите на временную метку между каждой задачей, вы можете обнаружить загадочную задержку в 1000 миллисекунд при каждом переходе к следующей задаче.Я предполагаю, что в plinq или задании есть механизм, который в какой-то ситуации приостанавливается на одну секунду, что значительно замедляет процесс.
Благодаря объяснению @StephenCleary теперь я понимаю, что задержка происходит из-засоздание темы.Я снова настраиваю свой эксперимент и обнаруживаю, что метод ForAll
будет блокировать задачу до тех пор, пока все другие методы ForAll
в других задачах не будут завершены.
static Task Run( int scale, int id )
{
return Task.Run( () =>
{
Enumerable.Range( 0, scale ).AsParallel().ForAll( j =>
{
for ( int k = 0; k < scale; k++ )
{
}
Console.WriteLine( $"[{DateTimeOffset.Now.ToUnixTimeMilliseconds()}]Task {id} for loop {j} end, thread count = {Process.GetCurrentProcess().Threads.Count}" );
} );
Console.WriteLine( $"[{DateTimeOffset.Now.ToUnixTimeMilliseconds()}]Task {id} finished" );
} );
}
И в результате:
[1557478553656]Task 6 for loop 6 end, thread count = 19
[1557478553657]Task 6 for loop 7 end, thread count = 19
[1557478554645]Task 7 for loop 0 end, thread count = 20
[1557478554647]Task 7 for loop 1 end, thread count = 20
[1557478554649]Task 7 for loop 2 end, thread count = 20
[1557478554651]Task 7 for loop 3 end, thread count = 20
[1557478554653]Task 7 for loop 4 end, thread count = 20
[1557478554655]Task 7 for loop 5 end, thread count = 20
[1557478554657]Task 7 for loop 6 end, thread count = 20
[1557478554659]Task 7 for loop 7 end, thread count = 20
[1557478555644]Task 1 finished
[1557478555644]Task 0 finished
[1557478555644]Task 3 finished
[1557478555644]Task 2 finished
[1557478555644]Task 4 finished
[1557478555644]Task 6 finished
[1557478555644]Task 5 finished
[1557478555644]Task 7 finished
Я ожидаю, что ForAll
метод должен вернуться немедленно.Почему это блокирует задачу и поток?