C# LINQ选择模拟异步方法
假设我有以下示例代码:C# LINQ选择模拟异步方法,c#,.net,linq,async-await,C#,.net,Linq,Async Await,假设我有以下示例代码: private static async Task Main(string[] args) { var result = Enumerable.Range(0, 3).Select(x => TestMethod(x)).ToArray(); Console.ReadKey(); } private static int TestMethod(int param) { Console.WriteLine($"{param} before")
private static async Task Main(string[] args)
{
var result = Enumerable.Range(0, 3).Select(x => TestMethod(x)).ToArray();
Console.ReadKey();
}
private static int TestMethod(int param)
{
Console.WriteLine($"{param} before");
Thread.Sleep(50);
Console.WriteLine($"{param} after");
return param;
}
TestMethod将运行3次直到完成,因此我将在之前和之后看到3对:
0 before
0 after
1 before
1 after
2 before
2 after
现在,我需要使TestMethod异步:
private static async Task<int> TestMethod(int param)
{
Console.WriteLine($"{param} before");
await Task.Delay(50);
Console.WriteLine($"{param} after");
return param;
}
请注意,我不想并行运行所有3个调用-我需要它们逐个执行,只有在前一个调用完全完成并返回值时才开始下一个调用。您可以使用同步原语
类程序
{
静态异步任务主(字符串[]args)
{
var waitHandle=新的自动重置事件(true);
var结果=可枚举
.范围(0,3)
.选择(异步(int参数)=>{
waitHandle.WaitOne();
等待测试方法(param);
waitHandle.Set();
}).ToArray();
Console.ReadKey();
}
私有静态异步任务TestMethod(int参数)
{
WriteLine($“{param}before”);
等待任务。延迟(50);
WriteLine($“{param}after”);
返回参数;
}
}
我经常遇到这个需求,我不知道C#7.2中有什么内置解决方案可以解决这个问题。我通常会在foreach
中的每个异步操作上使用wait
,但您可以使用扩展方法:
public static class EnumerableExtensions
{
public static async Task<IEnumerable<TResult>> SelectAsync<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, Task<TResult>> asyncSelector)
{
var results = new List<TResult>();
foreach (var item in source)
results.Add(await asyncSelector(item));
return results;
}
}
这种方法的缺点是,SelectAsync
是渴望的,而不是懒惰的。C#8承诺引入,这将允许它再次懒惰。您必须知道什么是可枚举对象。可枚举对象不是枚举本身。它提供了获取枚举器对象的可能性。如果你有一个枚举器对象,你可以要求序列的第一个元素,一旦你有一个枚举元素,你可以要求下一个
所以,创建一个能够在序列上枚举的对象和枚举本身并没有任何关系
Select函数只返回可枚举对象,不启动枚举。枚举由ToArray
启动
在其最低级别,枚举按如下方式进行:
IEnumerable<TSource> myEnumerable = ...
IEnumerator<TSource> enumerator = myEnumerable.GetEnumerator();
while (enumerator.MoveNext())
{
// there is still an element in the enumeration
TSource currentElement = enumerator.Current;
Process(currentElement);
}
您只需创建一次。您可以将其用于任何需要等待每个元素的ToListSync:
var result = Enumerable.Range(0, 3)
.Select(i => TestMethod(i))
.ToListAsync();
请注意,Select
的返回值是IEnumerable
:一个可以枚举一系列Task
对象的对象。在foreach中,您在(撰写本文时的下一个主要版本)中获得的每个周期都有一个任务将支持IAsyncEnumrable
,您可以为每个循环编写异步:
await foreach (var item in GetItemsAsync())
// Do something to each item in sequence
我希望会有一个Select
扩展方法来进行投影,但如果没有,那么编写自己的方法并不困难。您还可以使用yield-return
创建IAsyncEnumrable
迭代器块除了手动枚举之外,目前似乎没有其他解决方案(直到C#8.0)。我为此做了一个扩展方法(返回一个列表而不是数组,因为它更简单):
公共静态异步任务ToListSync(此IEnumerable源)
{
var result=新列表();
foreach(源中的var项)
{
结果。添加(等待项);
}
返回结果;
}
我想这就是OP真正想要的,因为我花了一整天的时间解决了OP想知道的问题。但我想用流利的方法完成
根据Martin Liversage的回答,我找到了正确的方法:IAsyncEnumerable
和System.Linq.Async
pacake,它们在C#8.0中提供,正是我想要的
例如:
private static async Task Main(string[] args)
{
var result = await Enumerable.Range(0, 3)
.ToAsyncEnumerable()
.SelectAwait(x => TestMethod(x))
.ToArrayAsync();
Console.ReadKey();
}
private static async Task<int> TestMethod(int param)
{
Console.WriteLine($"{param} before");
await Task.Delay(50);
Console.WriteLine($"{param} after");
return param;
}
专用静态异步任务主(字符串[]args)
{
var结果=等待可枚举范围(0,3)
.ToAsyncEnumerable()
.SelectAwait(x=>TestMethod(x))
.ToArrayAsync();
Console.ReadKey();
}
私有静态异步任务TestMethod(int参数)
{
WriteLine($“{param}before”);
等待任务。延迟(50);
WriteLine($“{param}after”);
返回参数;
}
foreach
怎么办?@JSteward绝对是一个选项,但LINQ风格要短得多,优雅得多。我将为每个人创建一个扩展方法wrappingforeach
,我只是希望在标准库中已经有了一个解决方案。@ AlkeSeSouBin你认为我的答案是正确的还是不正确的?所以你是说其他的要求规定了<代码>测试方法< /C> >代码>异步< /代码>,但是在这个特定的情况下,你想保持它同步运行吗?如果不是,我看不出有任何理由使方法异步
。如果是的话,您可以返回TestMethod(x).Result
@GertArnold我想异步运行它,但不是并行运行-这有点不同。在我的实际任务中,我希望能够多次调用实体框架的DbContext.savechangesync
,它不允许并行执行,但异步调用仍然有一个优点——它不会阻止调用线程。我想知道我做错了什么,应该投反对票。如果你在否决投票时不发表评论,我就无法改进我的答案,也永远不会知道什么是错误的。我也想知道为什么有人否决了投票。关于可枚举的非常有趣的信息,我没有意识到。我找到了一个非常类似的ToListAsync
扩展方法,除了我使用了foreach
而不是直接使用Enumerable(参见我添加的答案)。我的理解是,foreach
在引擎盖下做的基本相同。是正确的,还是遗漏了什么?foreach在内部调用GetEnumerator()
/MoveNext()
/Current
。因此,如果您想返回IEnumerable
而不是列表,通常使用foreach和产生返回
要容易得多
var result = Enumerable.Range(0, 3)
.Select(i => TestMethod(i))
.ToListAsync();
await foreach (var item in GetItemsAsync())
// Do something to each item in sequence
public static async Task<List<T>> ToListAsync<T>(this IEnumerable<Task<T>> source)
{
var result = new List<T>();
foreach (var item in source)
{
result.Add(await item);
}
return result;
}
private static async Task Main(string[] args)
{
var result = await Enumerable.Range(0, 3)
.ToAsyncEnumerable()
.SelectAwait(x => TestMethod(x))
.ToArrayAsync();
Console.ReadKey();
}
private static async Task<int> TestMethod(int param)
{
Console.WriteLine($"{param} before");
await Task.Delay(50);
Console.WriteLine($"{param} after");
return param;
}