C# 并行运行任务并连接输出
我想获取多个数据提供者,它们返回相同的数据结构,但数据输出不同。最后,需要追加数据源的输出,以便我可以使用全部结果。为了提高性能,需要并行调用这些数据源。我现在有了这个解决方案:C# 并行运行任务并连接输出,c#,.net,.net-core,task-parallel-library,C#,.net,.net Core,Task Parallel Library,我想获取多个数据提供者,它们返回相同的数据结构,但数据输出不同。最后,需要追加数据源的输出,以便我可以使用全部结果。为了提高性能,需要并行调用这些数据源。我现在有了这个解决方案: Task<List<Result>> dataSource1 = null; Task<List<Result>> dataSource2 = null; foreach (var dataSource in dataSourcesToBeFetched)
Task<List<Result>> dataSource1 = null;
Task<List<Result>> dataSource2 = null;
foreach (var dataSource in dataSourcesToBeFetched)
{
switch (dataSource)
{
case DataSource.DataSource1:
dataSource1 = DataSource1();
break;
case DataSource.DataSource2:
dataSource2 =DataSource2();
break;
}
}
await Task.WhenAll(dataSource1, dataSource2);
var allData = dataSource1.Result.Append(dataSource2.Result)
任务数据源1=null;
任务数据源2=null;
foreach(dataSourcesToBeFetched中的var数据源)
{
交换机(数据源)
{
案例数据源.DataSource1:
dataSource1=dataSource1();
打破
案例数据源.DataSource2:
dataSource2=dataSource2();
打破
}
}
等待任务。WhenAll(数据源1、数据源2);
var allData=dataSource1.Result.Append(dataSource2.Result)
但我对此并不满意。添加更多数据源时,我需要将新结果附加到列表中,这看起来很难看。除此之外,我想使用switch表达式,但我正在努力解决这个问题。所有这些代码都可以替换为:
var results=await Task.WhenAll(DataSource1(),DataSource2());
该方法返回一个带有所有异步操作结果的任务
获得结果后,您可以将其与可枚举的合并。选择many
:
var flattened=results.SelectMany(r=>r).ToList();
虽然可以将这两种操作结合起来,但最好避免。这导致代码难以读取、维护和调试。在调试期间,您通常希望在等待
后中断,以检查结果是否为空或其他意外值
任务和扁平化在不同的线程上运行,这使得使用链接调用进行调试变得更加困难
如果确实需要,可以在whalll
之后使用ContinueWith
在返回结果之前在线程池线程中处理结果:
var flatten=await Task.WhenAll(DataSource1(),DataSource2())
.ContinueWith(t=>t.Results.SelectMany(r=>r)
.ToList());
更新
要过滤源,一种快速而肮脏的方法是创建一个字典
,将源ID映射到方法,并使用LINQ的选择
来选择它们:
//In a field
Dictionary<DataSource,Func<Task<List<Result>>>> map=new (){
[DataSource.Source1]=DataSource1,
[DataSource.Source1]=DataSource2
};
//In the method
DataSource[] fetchSources=new DataSource[0];
var tasks=fetchSources.Select(s=>map[s]());
//在字段中
字典映射=新建(){
[DataSource.Source1]=数据源1,
[DataSource.Source1]=数据源2
};
//在方法中
DataSource[]fetchSources=新数据源[0];
var tasks=fetchSources.Select(s=>map[s]());
但这与使用函数执行相同的任务没有什么不同:
DataSource[] fetchSources=new DataSource[0];
var tasks=fetchSources.Select(s=>RunSource(s));
//or even
//var tasks=fetchSources.Select(RunSource);
var results=await Task.WhenAll(tasks);
var flattened=results.SelectMany(r=>r).ToList();
public static Task<List<Result>> RunSource(DataSource source)
{
return source switch {
DataSource.Source1=> DataSource1(),
DataSource.Source2=> DataSource2(),
_=>throw new ArgumentOutOfRangeException(nameof(source))
};
}
DataSource[]fetchSources=新数据源[0];
var tasks=fetchSources.Select(s=>RunSource);
//甚至
//var tasks=fetchSources.Select(RunSource);
var结果=等待任务.WhenAll(任务);
var=results.SelectMany(r=>r.ToList();
公共静态任务运行源(数据源)
{
返回源开关{
DataSource.Source1=>DataSource1(),
DataSource.Source2=>DataSource2(),
_=>抛出新ArgumentOutOfRangeException(名称(源))
};
}
代码中的一个问题是,如果要删除的数据源中不存在数据源.DataSource1
,则您正在等待一个空任务
我可能会选择一系列有待完成的任务
比如:
var dataSources = new List<Task<List<Result>>>();
// check if the DataSource1 is present in the dataSourcesToBeFetched
if(dataSourcesToBeFetched.Any(i => i == DataSource.DataSource1))
dataSources.Add(DataSource1());
// check if the DataSource2 is present in the dataSourcesToBeFetched
if(dataSourcesToBeFetched.Any(i => i == DataSource.DataSource2))
dataSources.Add(DataSource2());
// a list to hold all results
var allData = new List<Result>();
// if we need to fetch any, await all tasks.
if(dataSources.Count > 0)
{
await Task.WhenAll(dataSources);
// add the results to the list.
foreach(var dataSource in dataSources)
allData.AddRange(dataSource.Result);
}
var dataSources=newlist();
//检查要删除的数据源中是否存在数据源1
if(dataSourcesToBeFetched.Any(i=>i==DataSource.DataSource1))
Add(DataSource1());
//检查数据源2是否存在于要删除的数据源中
if(dataSourcesToBeFetched.Any(i=>i==DataSource.DataSource2))
Add(DataSource2());
//保存所有结果的列表
var allData=新列表();
//如果我们需要取,等待所有任务。
如果(dataSources.Count>0)
{
等待任务WhenAll(数据源);
//将结果添加到列表中。
foreach(数据源中的var数据源)
allData.AddRange(dataSource.Result);
}
任务。当所有的
都将返回所有结果。@Rogier为什么使用任务。运行
进行I/O操作?@PeterCsala问得好。确实不需要。让我更新一下示例。一个简单的var results=wait Task.whalll(DataSource1(),DataSource2())
就足够了。由于所有异步操作都会产生相同的输出,结果
将是一个数组,其中包含所有异步操作的输出。您不希望等待null
,如果要消除的数据源包含重复项,则可以覆盖数据源1
(和另一个)以展平结果,您可以使用(等待任务。当所有(…).SelectMany(x=>x).ToList()
@JL0PD更好地使用结果。SelectMany()
相反。在这种情况下,链接方法会导致难以读取和维护的代码。异步操作不仅仅是要链接的调用,还有不同类型的错误。在调试过程中,在wait
调用时或之后中断的可能性要比在合并后通过dataSourcesToBeFe循环要大得多tched
因为并非所有的数据源都需要获取。有时只有一个,或者所有的数据源。您将如何实现这一部分?@Rogier问题代码的编写方式,看起来您使用了开关
只分配给变量。您的实际问题是什么?不管是什么,可能有很多更简单的方法。如果有任何问题,您可以使用LINQ和Where
来过滤项目,选择返回任务
对象并等待它们。或者,如果您有ETL问题,您可以使用数据流类和LinkTo
或TransformManyBlock
路由/合并消息,分组块etc@Rogier我添加了两个示例选择要使用的源代码-一个带有字典,一个带有选择器函数。在您的案例中,您应该使用任何更容易使用的源代码