我正在尝试用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);
}