Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/315.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# Parallel.ForEach内的多个异步等待链_C#_Asynchronous_Async Await_Task Parallel Library_Parallel.foreach - Fatal编程技术网

C# Parallel.ForEach内的多个异步等待链

C# Parallel.ForEach内的多个异步等待链,c#,asynchronous,async-await,task-parallel-library,parallel.foreach,C#,Asynchronous,Async Await,Task Parallel Library,Parallel.foreach,我有一个Parallel.ForEach循环,它在集合中循环。在循环内部,我进行多个网络I/O调用。我使用Task.ContinueWith并嵌套了后续的异步等待调用。处理的顺序并不重要,但是来自每个异步调用的数据应该以同步的方式处理。含义-对于每个迭代,从第一个异步调用检索到的数据应该传递给第二个异步调用。第二个异步调用完成后,来自两个异步调用的数据应该一起处理 Parallel.ForEach(someCollection, parallelOptions, async (item, sta

我有一个Parallel.ForEach循环,它在集合中循环。在循环内部,我进行多个网络I/O调用。我使用Task.ContinueWith并嵌套了后续的异步等待调用。处理的顺序并不重要,但是来自每个异步调用的数据应该以同步的方式处理。含义-对于每个迭代,从第一个异步调用检索到的数据应该传递给第二个异步调用。第二个异步调用完成后,来自两个异步调用的数据应该一起处理

Parallel.ForEach(someCollection, parallelOptions, async (item, state) =>
{
    Task<Country> countryTask = Task.Run(() => GetCountry(item.ID));

    //this is my first async call
    await countryTask.ContinueWith((countryData) =>
    {
        countries.Add(countryData.Result);

        Task<State> stateTask = Task.Run(() => GetState(countryData.Result.CountryID));

        //based on the data I receive in 'stateTask', I make another async call
        stateTask.ContinueWith((stateData) =>
        {
            states.Add(stateData.Result);

            // use data from both the async calls pass it to below function for some calculation
            // in a synchronized way (for a country, its corresponding state should be passed)

            myCollection.ConcurrentAddRange(SomeCalculation(countryData.Result, stateData.Result));
        });
    });
});
Parallel.ForEach(someCollection,parallelOptions,异步(项,状态)=>
{
Task countryTask=Task.Run(()=>GetCountry(item.ID));
//这是我的第一个异步调用
等待countryTask。继续((countryData)=>
{
countries.Add(countryData.Result);
Task stateTask=Task.Run(()=>GetState(countryData.Result.CountryID));
//根据我在“stateTask”中收到的数据,我进行另一个异步调用
stateTask.ContinueWith((stateData)=>
{
states.Add(stateData.Result);
//使用来自两个异步调用的数据将其传递给下面的函数进行一些计算
//以同步方式(对于一个国家,应传递其对应的状态)
myCollection.ConcurrentAddRange(SomeCalculation(countryData.Result,stateData.Result));
});
});
});
我在没有使用continue wait的情况下尝试了上述方法,但它没有以同步方式工作。现在,上面的代码执行到完成,但没有处理任何记录


有人帮我吗?如果我能补充更多细节,请告诉我。

我认为你把事情复杂化了;在
Parallel.ForEach
中,您已经在线程池中,因此在线程池中创建大量额外任务实际上没有任何好处。所以如何做到这一点实际上取决于
GetState
etc是同步的还是异步的。如果我们假设同步,则类似于:

Parallel.ForEach(someCollection,parallelOptions,(item,))=>
{
var country=GetCountry(item.Id);
countries.Add(country);//警告:可能需要同步
var state=GetState(country.CountryID);
states.Add(state);//警告:可能需要同步
//使用来自两个异步调用的数据将其传递给下面的函数进行一些计算
//以同步方式(对于一个国家,应传递其对应的状态)
myCollection.ConcurrentAddRange(某些计算(国家、州));
});
如果它们是异步的,则会变得更加尴尬;如果我们能做一些事情,比如:

//警告:危险代码-请勿复制
Parallel.ForEach(someCollection、parallelOptions、async(item,))=>
{
var country=await GetCountryAsync(item.Id);
countries.Add(country);//警告:可能需要同步
var state=await-getstatesync(country.CountryID);
states.Add(state);//警告:可能需要同步
//使用来自两个异步调用的数据将其传递给下面的函数进行一些计算
//以同步方式(对于一个国家,应传递其对应的状态)
myCollection.ConcurrentAddRange(某些计算(国家、州));
});
但是这里的问题是,
Parallel.ForEach
中没有一个回调是“可等待的”,这意味着:我们在这里无声地创建了一个
async void
回调,这是非常糟糕的。这意味着,
Parallel.ForEach
将在未完成的
wait
发生时认为它已经“完成”,这意味着:

  • 我们不知道所有的工作何时实际完成
  • 您可以同时执行比您预期的更多的操作(不能遵守max dop)

  • 目前似乎没有任何好的API可以避免这种情况。

    由于您的方法涉及I/O,因此应该将它们编写为真正的异步,而不仅仅是使用
    任务在线程池上同步运行。运行

    然后您可以将
    任务与
    可枚举组合使用。选择

    var tasks = someCollection.Select(async item =>
    {
        var country = await GetCountryAsync(item.Id);
        var state = await GetStateAsync(country.CountryID);
        var calculation = SomeCalculation(country, state);
    
        return (country, state, calculation);
    });
    
    foreach (var tuple in await Task.WhenAll(tasks))
    {
        countries.Add(tuple.country);
        states.Add(tuple.state);
        myCollection.AddRange(tuple.calculation);
    }
    
    这将确保每个
    国家
    计算
    按顺序进行,但每个
    项目
    都是同步和异步处理的


    根据评论更新

    using var semaphore = new SemaphoreSlim(2);
    using var cts = new CancellationTokenSource();
    
    int failures = 0;
    
    var tasks = someCollection.Select(async item =>
    {
        await semaphore.WaitAsync(cts.Token);
        
        try
        {
            var country = await GetCountryAsync(item.Id);
            var state = await GetStateAsync(country.CountryID);
            var calculation = SomeCalculation(country, state);
    
            Interlocked.Exchange(ref failures, 0);
    
            return (country, state, calculation);
        {
        catch
        {
            if (Interlocked.Increment(ref failures) >= 10)
            {
                cts.Cancel();
            }
            throw;
        }
        finally
        {
            semaphore.Release();
        }
    });
    
    信号量确保最多2个并发异步操作,取消令牌将在10个连续异常后取消所有未完成的任务

    联锁
    方法确保以线程安全的方式访问
    故障


    进一步更新

    使用2个信号量来防止多次迭代可能更有效

    将所有添加列表封装到单个方法中:

    void AddToLists(Country country, State state, Calculation calculation)
    {
        countries.Add(country);
        states.Add(state);
        myCollection.AddRange(calculation);
    }
    
    然后,您可以允许2个线程同时为Http请求提供服务,1个线程执行添加,从而使该操作线程安全:

    using var httpSemaphore = new SemaphoreSlim(2);
    using var listAddSemaphore = new SemaphoreSlim(1);
    using var cts = new CancellationTokenSource();
    
    int failures = 0;
    
    await Task.WhenAll(someCollection.Select(async item =>
    {
        await httpSemaphore.WaitAsync(cts.Token);
        
        try
        {
            var country = await GetCountryAsync(item.Id);
            var state = await GetStateAsync(country.CountryID);
            var calculation = SomeCalculation(country, state);
    
            await listAddSemaphore.WaitAsync(cts.Token);
            AddToLists(country, state, calculation);
    
            Interlocked.Exchange(ref failures, 0);
        {
        catch
        {
            if (Interlocked.Increment(ref failures) >= 10)
            {
                cts.Cancel();
            }
            throw;
        }
        finally
        {
            httpSemaphore.Release();
            listAddSemaphore.Release();
        }
    }));
    

    在同一代码中混合使用
    wait
    ContinueWith
    似乎很奇怪,使用
    任务也很奇怪。如果您已经在TP上,请运行
    。。。看看重构它,但是。。。这不仅仅是有点奇怪@马歇尔:也许你是对的。我的目的是并行处理发生多个netowork I/O调用的数据,但以同步方式进行。一般来说,您不应该使用
    ContinueWith
    。。。几乎从来没有过,
    Parallel.ForEach
    。lambda通过了。该方法将在启动异步操作后立即完成,
    MaxDegreeOfParallelism
    选项将不被考虑。。。很多讨厌的问题。此方法仅用于CPU限制的工作。您上面解释的异步方法是我的第一次尝试,其中Parallel.ForEach没有等待调用完成。然后我做了一些类似于这里解释的事情:。问题在于两个异步调用之间的同步,其中