作为异步和等待的最佳实践的一部分,建议不要使用Task.Run。我有一个服务,可以多次调用第三方服务,而我们使用async来进行这些调用。我正在下面的代码中寻求有关代码改进的建议。
public interface IRouteService
{
Task<IEnumerable<Route>> GetRoute(Coordinates orign, Coordinates destination);
}
public class RouteProvider
{
private readonly IRouteService _routeService;
public RouteProvider(IRouteService routeService)
{
_routeService = routeService;
}
public async Task<IEnumerable<Route>> GetRoutes(IEnumerable<Coordinates> origns, IEnumerable<Coordinates> destinations)
{
ConcurrentBag<Route> routes = new ConcurrentBag<Route>();
List<Task> tasks = new List<Task>();
foreach (var origin in origns)
{
foreach (var destination in destinations)
{
tasks.Add(Task.Run(async () =>
{
var response= await _routeService.GetRoute(origin, destination);
foreach (var item in response)
{
routes.Add(item);
}
}));
}
}
Task.WaitAll(tasks.ToArray());
return routes;
}
}
public class Route
{
public string Distance { get; set; }
public Coordinates Origin { get; set; }
public object Destination { get; set; }
public string OriginName { get; set; }
public string DestinationName { get; set; }
}
public class Coordinates
{
public float Lat { get; set; }
public float Long { get; set; }
}
答案 0 :(得分:2)
对于这样的问题,使用LINQ很方便。 LINQ产生不可变的结果,因此您可以避免并发问题,并且不需要任何专门的集合。
通常,使用LINQ或类似的编程技术(即像函数程序员一样思考)将使multithreading much easier。
public async Task<IEnumerable<Route>> GetRoutes(IEnumerable<Coordinates> origins, IEnumerable<Coordinates> destinations)
{
var tasks = origins
.SelectMany
(
o => destinations.Select
(
d => _routeService.GetRoute(o, d)
)
);
await Task.WhenAll( tasks.ToArray() );
return tasks.SelectMany( task => task.Result );
}
答案 1 :(得分:0)
正如评论中指出的那样,我建议您可以使用Task.WhenAll()
确定要完成的所有任务,并使用return await Task.WhenAll(tasks);
获得结果。为此,您可以如下所示更新代码。
public async Task<IEnumerable<Route>> GetRoutes(IEnumerable<Coordinates> origns, IEnumerable<Coordinates> destinations)
{
ConcurrentBag<Route> routes = new ConcurrentBag<Route>();
List<Task> tasks = new List<Task>();
foreach (var origin in origns)
{
foreach (var destination in destinations)
{
tasks.Add(_routeService.GetRoute(origin, destination));
}
}
var response = await Task.WhenAll(tasks);
foreach (var item in response)
{
routes.Add(item);
}
return routes;
}
}
由于所有调用将返回相同的类型,因此您无需在其他循环中再启动第二个foreach
。同样,通过这种方式,您将避免使用Task.WaitAll()
锁定线程执行,并且程序将同步运行。要查看WhenAll()
与WaitAll()
之间的区别,可以签出this。
答案 2 :(得分:0)
您可以使用延续来代替使用Task.Run
方法直接创建任务。
foreach (var origin in origns)
{
foreach (var destination in destinations)
{
tasks.Add(
_routeService.GetRoute(origin, destination)
.ContinueWith(response =>
{
foreach (var item in response.Result)
routes.Add(item);
})
);
}
}
因此,GetRoute
方法将异步执行,而无需创建单独的线程。从中获得的结果将在单独的线程(任务)中进行处理。
但是,只有在结果需要很长时间才能处理时才需要这样做。否则,根本不需要单独的线程。