C#ForEachAsync过早退出

时间:2020-03-04 09:57:32

标签: c# .net-core

我正在尝试用ForEachAsync的Stephen Toub's implementation替换代码中的一个简单的foreach循环。

所以基本上我有以下内容:

internal async Task GatherPreviousPositions(string companyId, long vehiculeId, AlertContract oneAlert, string positionTimeString, int positionInacurracyThreshold, CancellationToken cancellationToken)
{
    // Look for previous positions
    var previousPositions = await DapperCommand.RunSQLCommandAsync<dynamic>(
        @"SELECT TOP 5 - ROW_NUMBER() OVER( ORDER BY [Time] DESC) AS step ,[Time]
                              ,[VehiculeID]
                              ,[VehiculeUserID]
                              ,[Latitude]
                              ,[Longitude]
                              ,[Speed]
                              ,[Heading]
                              ,[Altitude]
                              ,[SatelliteCount]
                              ,[HDOP]
                              ,[VDOP]
                              ,[EngineOn]
                              ,[PrivacyOn]
                              ,[Mileage]
                              ,[DbInsertTime]
                              ,[ReceivedTime]
                          FROM [RTEGeoloc].[dbo].[GpsPosition]
                          WHERE [Time] < @Time
                            AND VehiculeID = @VehiculeID
                            ORDER BY [Time] DESC",
        new { Time = positionTimeString, VehiculeID = vehiculeId },
        true,
        "previousPositions",
        this.logger,
        null,
        false,
        cancellationToken)
        .ConfigureAwait(continueOnCapturedContext: false);

        // Reverse gelocalize the positions
    foreach (dynamic pp in previousPositions)
    {
        double ppLatitude = (double)((IDictionary<string, object>)pp)["Latitude"];
        double ppLongitude = (double)((IDictionary<string, object>)pp)["Longitude"];
        string cacheKey = $@"{ppLatitude.RoundDown(5)},{ppLongitude.RoundDown(5)}";
        var addressOrPoi = await dataCache.GetOrCreateLazyAsync(key: cacheKey, dbKey: int.Parse(companyId, CultureInfo.CurrentCulture), $@"{ppLatitude};{ppLongitude}", factoryMethod: ReverseGeocodingFactory(), ct: cancellationToken).ConfigureAwait(false);

        oneAlert.AlertSteps.Add(new AlertStep
        {
            Age = oneAlert.TimestampAlertLocation != 0 ? oneAlert.TimestampAlertLocation - ((DateTime)((IDictionary<string, object>)pp)["Time"]).Ticks : 0,
            Step = $@"{(int)(long)((IDictionary<string, object>)pp)["step"]}",
            LocationName = addressOrPoi.Type == AlertLocationType.POI ? addressOrPoi.Value : string.Empty,
            DetailedMailingAddress = addressOrPoi.Address ?? string.Empty,
            Coordinate = new Coordinates { Latitude = (float)ppLatitude, Longitude = (float)ppLongitude },
            IsOldAlertLocation = false,
            IsInaccurateAlertLocation = ((IDictionary<string, object>)pp)["HDOP"] != null ? ToAccuracy((float)((IDictionary<string, object>)pp)["HDOP"]).GetValueOrDefault() > positionInacurracyThreshold : false,
            LocationSource = addressOrPoi.Type == AlertLocationType.POI ? "BALISE" : "GPS",
        });
    }
}

如您所见,我将从数据库中收集多达5条记录。我想并行处理它们,最多并发4个任务。

foreach循环变为:

 await previousPositions.ForEachAsync(4, async pp =>
{
...
}).ConfigureAwait(false);

此异步foreach循环完成后,我可以看到oneAlert.AlertSteps.Add(new AlertStep{...});仅被调用了4次,因为我的列表中有4个元素,而没有按预期的5个。

我已经测试了numerous implementations在这里和博客中发现的问题,它们都遇到相同的问题。

我因为看不见东西而想念东西。


编辑:以下是在添加呼叫期间防止出现竞争状况的解决方案。

   // Reverse gelocalize the positions
    using (SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1))
    {
        //foreach (dynamic pp in previousPositions)
        await previousPositions.ForEachAsync(4,
        async pp =>
        {
            double ppLatitude = (double)((IDictionary<string, object>)pp)["Latitude"];
            double ppLongitude = (double)((IDictionary<string, object>)pp)["Longitude"];
            string cacheKey = $@"{ppLatitude.RoundDown(5)},{ppLongitude.RoundDown(5)}";
            var addressOrPoi = await dataCache.GetOrCreateLazyAsync(key: cacheKey, dbKey: int.Parse(companyId, CultureInfo.CurrentCulture), $@"{ppLatitude};{ppLongitude}", factoryMethod: ReverseGeocodingFactory(), ct: cancellationToken).ConfigureAwait(false);

            await semaphoreSlim.WaitAsync().ConfigureAwait(false);
            try
            {
                oneAlert.AlertSteps.Add(new AlertStep
                {
                    Age = oneAlert.TimestampAlertLocation != 0 ? oneAlert.TimestampAlertLocation - ((DateTime)((IDictionary<string, object>)pp)["Time"]).Ticks : 0,
                    Step = $@"{(int)(long)((IDictionary<string, object>)pp)["step"]}",
                    LocationName = addressOrPoi.Type == AlertLocationType.POI ? addressOrPoi.Value : string.Empty,
                    DetailedMailingAddress = addressOrPoi.Address ?? string.Empty,
                    Coordinate = new Coordinates { Latitude = (float)ppLatitude, Longitude = (float)ppLongitude },
                    IsOldAlertLocation = false,
                    IsInaccurateAlertLocation = ((IDictionary<string, object>)pp)["HDOP"] != null ? ToAccuracy((float)((IDictionary<string, object>)pp)["HDOP"]).GetValueOrDefault() > positionInacurracyThreshold : false,
                    LocationSource = addressOrPoi.Type == AlertLocationType.POI ? "BALISE" : "GPS",
                });
            }
            finally
            {
                semaphoreSlim.Release();
            }
        }).ConfigureAwait(false);
    }

0 个答案:

没有答案