Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/324.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# LINQ选择模拟异步方法_C#_.net_Linq_Async Await - Fatal编程技术网

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风格要短得多,优雅得多。我将为每个人创建一个扩展方法wrapping
foreach
,我只是希望在标准库中已经有了一个解决方案。@ 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;
}