在控制台应用程序中浏览一些代码时,我在SecondInitialize
函数中看到了嵌套的Task.WhenAll。我决定使用较大的“位置列表”测试此功能,然后查看其反应。
我看到的是,大约有100个位置,100 * 100 = 10,000个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);
}
答案 0 :(得分:1)
问题似乎在于如何从连接一组位置(起点和目的地?)的所有行程中计算路线,并计算每条路线的长度(费用?)。费用昂贵的工作似乎是对_routeService.Route
和ToTripLength
的呼吁。
从100个位置计算10K组合很简单,不需要并行化。一个简单的LINQ查询将起作用:
var combinations=( from start in locations
from finish in locations
where start!=finish
select (start,finish))
.ToArray();
此后会发生什么取决于_routeService.Route
的行为。如果它是本地库,这是一个数据并行性问题,试图以最有效的方式计算10K数据点。可以用PLINQ
如果是对外部服务的调用,则是并发性问题,不应浪费CPU时间等待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,在这种情况下是组合,将其划分为与内核一样多的分区,然后对每个分区使用 one 工作任务。每个分区的数据都馈送到其辅助任务,从而最大程度地降低了同步成本。
由于每次对Route
的调用都将在其中一个工作任务上运行,因此它可以用作快速且相当肮脏的方式来发出10K远程请求。这很浪费,因为它仅阻塞任务以等待响应。 WithDegreeOfParallelism可以用来比内核使用更多的工作任务,但这仍然浪费CPU等待响应的时间。阻塞调用在线程挂起之前从SpinWait开始,这意味着对远程服务的阻塞调用可以使用CPU内核,而无需执行任何操作。这会严重损害服务器环境上的可伸缩性。
var tripLengths = from cmb in combinations.AsParallel()
.WithDegreeOfParalellism(10)
let routeParams=locationsToParams(cmb)
let result=callRoute(routeParams)
select ToTripLength(result);
var finalResults = tripLengths.ToArray();
答案 1 :(得分:0)
由于您的示例不完整且无法编译,因此很难看到您到底想做什么。
但据我所知有几个问题:
在任务can lead to deadlocks上调用Wait
(或Result
)。
使用ConfigureAwait( false )
将有助于避免此类问题,但不能消除所有问题。
因此,当您要访问任务的结果时,最好始终等待它。
我看不到您在Task.WhenAll
内嵌套Task.WhenAll
所要完成的工作。
WhenAll
返回一个任务,您可以在没有Task.WhenAll
的情况下等待。
您创建的每个任务都会增加一些性能开销,因此您应该尝试创建尽可能少的任务。
使用Task.Run
和一个异步委托来等待另一个任务(由Task.Run
创建)没有意义,您正在创建的任务超出了您的需要。
您可以等待一个任务。运行
我试图根据您的代码创建一个有效的示例(它不会做任何工作),以显示您应该更改的内容。 请注意,异步Main方法仅在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; }
}