Как решить задачи висит C #? - PullRequest
0 голосов
/ 22 января 2019

При просмотре некоторого кода в консольном приложении я увидел вложенный Task.WhenAll в функции SecondInitialize. Я решил протестировать эту функцию с большим списком расположений и посмотреть, как она отреагировала.

Что я видел, так это то, что при 100 местоположениях, 100 * 100 = 10000 Calculate вызовов, t.Wait() внутри Start требуется около 60 секунд, чтобы вернуться или иногда просто зависнуть полностью. Если я пытаюсь нажать Break All, консольное приложение даже не отвечает на мой щелчок, и происходит сбой в Visual Studio.

При использовании моей «Легче для чтения версии» внутри SecondInitialize также требуется некоторое время для возврата. Последовательное поведение.

Теперь странная часть: всякий раз, когда я использую отладчик и ставлю точку останова внутри SecondInitialize, а затем нажимаю продолжить, она заканчивается через 5-7 секунд.

Итак, мой вопрос: почему зависание обычно занимает много времени, когда я вижу, что оно быстрее, когда я отлаживаю внутри этой функции? Другой вопрос, правильно ли используется Tasks

public void Start()
{
    var t = CacheInitialize(locations, CancellationToken.None);
    t.Wait();
}

public Task CacheInitialize(IList<Location> locations, CancellationToken token)
{
    return SecondInitialize(locations, token);
}

public async Task SecondInitialize(IList<Location> locations, CancellationToken token)
{
    await Task.WhenAll(locations.Select(first =>
    {
        return Task.WhenAll(locations.Where(second => !second.Equals(first)).Select(second =>
        {
            return Calculate(first, second, token);
        }));
    }));

    //Easier to read version of ^
    //var tasks = locations.SelectMany(first => locations.Where(second => !second.Equals(first)).Select(second =>
    //{
    //  return Calculate(first, second, token);
    //}));
    //await Task.WhenAll(tasks);


    //No Tasks.
    //for (int x = 0; x < locations.Length; x++)
    //{
    //    for (int y = 0; y < locations.Length; y++)
    //    {
    //        if (x == y)
    //            continue;
    //        await Calculate(locations[x], locations[y], token).ConfigureAwait(false);
    //    }
    //}
}

public async Task<TripLength> Calculate(Location start, Location finish, CancellationToken token)
{
    if (start == finish)
        return TripLength.Zero;

    var parameters = new RouteParameters
    {
        Coordinates = new []
        {
            new Coordinate(start.Latitude, start.Longitude),
            new Coordinate(finish.Latitude, finish.Longitude)
        }
    };

    var route = await RunRoute(parameters, token);

    return ToTripLength(route);
}


protected Task<RouteResult> RunRoute(RouteParameters routeParams, CancellationToken token)
{
    return Task.Run(async () =>
    {
        var routingTask = Task.Run(() =>
        {
            RouteResult routeResults;
            var status = _routeService.Route(routeParams, out routeResults);
            return routeResults;
        }, token);
    return await routingTask.ConfigureAwait(false);

    }, token);
}

Ответы [ 2 ]

0 голосов
/ 22 января 2019

Похоже, проблема заключается в том, как рассчитать маршруты из всех рейсов, соединяющих набор местоположений (места происхождения и назначения?), И рассчитать длину (стоимость?) Каждого маршрута. Кажется, что дорогая работа - это вызов _routeService.Route и ToTripLength.

Вычисление 10K комбинаций из 100 локаций тривиально и не требует распараллеливания. Простой запрос LINQ будет работать:

var combinations=( from start in locations
                   from finish in locations
                   where start!=finish
                   select (start,finish))
                 .ToArray();

Что произойдет после этого, зависит от того, что делает _routeService.Route. Если это локальная библиотека, то это проблема параллелизма данных, которая пытается вычислить 10K точек данных наиболее эффективным способом. Это может быть обработано с помощью PLINQ

Если это вызов внешней службы, это проблема параллелизма , которая не должна тратить процессорное время на ожидание ответа 10K удаленных запросов.

Предполагая, что _routeService.Route - это локальная библиотека, можно использовать PLINQ. Однако пара вспомогательных методов облегчит написание запроса:

RouteParameters locationsToParams((Location start,Location finish) combination)
{
    return new RouteParameters {
        Coordinates = new[]
        {
            new Coordinate( start.Latitude, start.Longitude ),
            new Coordinate( finish.Latitude, finish.Longitude )
        }
    };
}

RouteResult  callRoute(RouteParameters routeParams)
{
    _routeService.Route(routeParams, out var routeResults);
    return routeResults;
}

var tripLengths = from cmb in combinations.AsParallel()
                  let routeParams=locationsToParams(cmb)
                  let result=callRoute(routeParams)
                  select ToTripLength(result);
var finalResults = tripLengths.ToArray();

AsParallel () будет принимать входной IEnumerable, в этом случае комбинации разбивают его на столько разделов, сколько имеется ядер, а затем используют одну рабочую задачу на раздел. Данные каждого раздела передаются его рабочей задаче, что сводит к минимуму затраты на синхронизацию.

Это может быть использовано как быстрый и довольно грязный способ сделать удаленные запросы 10K, так как каждый вызов Route будет выполняться для одной из рабочих задач. Это расточительно, потому что блокирует задачу только для ожидания ответа. WithDegreeOfParallelism можно использовать для использования большего количества рабочих задач, чем для ядер, но это по-прежнему тратит время ЦП в ожидании ответа. Блокирующие вызовы начинаются с SpinWait до приостановки потока, что означает, что блокирующий вызов удаленной службы может использовать ядро ​​ЦП, ничего не делая. Это может серьезно нанести ущерб масштабируемости в серверной среде.

var tripLengths = from cmb in combinations.AsParallel()
                                          .WithDegreeOfParalellism(10)
                  let routeParams=locationsToParams(cmb)
                  let result=callRoute(routeParams)
                  select ToTripLength(result);
var finalResults = tripLengths.ToArray();
0 голосов
/ 22 января 2019

Поскольку ваш пример не завершен и не может быть скомпилирован, трудно понять, что именно вы пытаетесь сделать.

Но, насколько я могу судить, есть несколько проблем:

  1. Вызов Wait (или Result) для задачи может привести к тупикам . Использование ConfigureAwait( false ) поможет избежать таких проблем, но не может устранить их все. Поэтому лучше всегда ждать Задачу, когда вы хотите получить доступ к ее результату.

  2. Я не вижу, чего вы пытаетесь достичь, вложив Task.WhenAll в Task.WhenAll. WhenAll возвращает одну задачу, которую вы можете просто ожидать без Task.WhenAll. Каждая создаваемая вами задача будет увеличивать производительность, поэтому вы должны попытаться создать как можно меньше задач.

  3. Использование Task.Run с асинхронным делегатом для ожидания другой задачи (созданной Task.Run) не имеет смысла, вы создаете больше задач, чем вам нужно. Вы можете просто ждать одну задачу. Run

Я попытался создать рабочий пример (он не будет работать) на основе вашего кода, чтобы показать, что вы должны изменить. Обратите внимание, что основной метод async доступен только в C # 7.1 или выше.

public class Program
{
    public static async Task Main( String[] args )
    {
        var foo = new Foo();

        var sw = Stopwatch.StartNew();

        await foo.Start();

        sw.Stop();

        Console.WriteLine($"Elapsed {sw.Elapsed} {sw.ElapsedMilliseconds}ms");
        Console.ReadLine();
    }
}

public class Foo
{
    public async Task CacheInitialize( IList<Location> locations, CancellationToken token ) =>
        await SecondInitialize( locations, token )
            .ConfigureAwait( false );

    public async Task<TripLength> Calculate( Location start, Location finish, CancellationToken token )
    {
        if ( start == finish )
            return TripLength.Zero;

        var parameters = new RouteParameters
        {
            Coordinates = new[]
            {
                new Coordinate( start.Latitude, start.Longitude ),
                new Coordinate( finish.Latitude, finish.Longitude )
            }
        };

        var route = await RunRoute( parameters, token );

        return new TripLength();
    }

    public async Task SecondInitialize( IList<Location> locations, CancellationToken token )
    {
        var tasks = new List<Task>( locations.Count );

        foreach ( var outer in locations )
        foreach ( var inner in locations )
        {
            if ( inner.Equals( outer ) )
                continue;

            tasks.Add( Calculate( outer, inner, token ) );
        }

        await Task.WhenAll( tasks );
    }

    public async Task Start()
    {
        var locations = new List<Location>();
        await CacheInitialize( locations, CancellationToken.None )
            .ConfigureAwait( false );
    }

    protected async Task<RouteResult> RunRoute( RouteParameters routeParams, CancellationToken token )
    {
        return await Task
                     .Run( () =>
                           {
                               //RouteResult routeResults;
                               //var status = _routeService.Route( routeParams, out routeResults );
                               //return routeResults;
                               return new RouteResult();
                           },
                           token )
                     .ConfigureAwait( false );
    }
}

public class Coordinate
{
    public Double Latitude { get; }
    public Double Longitude { get; }
    public Coordinate( Double latitude, Double longitude )
    {
        Latitude = latitude;
        Longitude = longitude;
    }
}
public class RouteParameters
{
    public Coordinate[] Coordinates { get; set; }
}
public class TripLength
{
    public static TripLength Zero = new TripLength();
}
public class RouteResult
{
}
public class Location
{
    public Double Latitude { get; }
    public Double Longitude { get; }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...