用线程/并行替换 foreach
Replacing foreach with Threading / Parallelism
我正在尝试缩短操作的执行时间。
我得到了一个用户列表,通过用户 ID,我正在执行一个服务计算一些数据的方法。在我通过用户 ID 获取数据后,我将它设置到每个实体,而不是仅仅保存上下文。
目前使用这个简单的代码大约需要 1 小时。每 100 个用户大约 21 - 24 秒。但这取决于每个用户。
using (var ctx = new MyContext())
{
var users = ctx.Users.Where(u => u.Status != -1).ToList();
foreach (var user in users)
{
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id);
}
await ctx.SaveChangesAsync();
}
我也这样试过,但需要更长的时间:
Parallel.ForEach(
users,
new ParallelOptions { MaxDegreeOfParallelism = -1 },
user => {
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id); }
);
**计算百分比**
public async Task<decimal> CalculatePercentage (int userId)
{
decimal completeness = 0;
bool? hasEducationsTags = null; //--> nullable boolea will handle 3 cases on SP side
bool? hasPosition = null; //--> nullable boolea will handle 3 cases on SP side
///-- check if user has any education
if(await SchoolService.HasEducations(userId).ConfigureAwait(false))
{
hasEducationsTags = await SchoolService.HasAnyFieldsOfStudy(user=>user.Id == userId);
}
///-- check if user can set position
if (await UserInfoService.CanSetLocation(userId).ConfigureAwait(false))
{
///-- check if position was set
string accountName = await UserInfoService.GetAccountName(userId).ConfigureAwait(false);
hasPosition = await HasUserSetPosition(accountName).ConfigureAwait(false);
}
///-- call store procedure
using (SqlConnection sqlConnection = ConnectionFactory.Create())
{
await sqlConnection.OpenAsync().ConfigureAwait(false);
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.CommandText = "ProfileCompletionAlgorithm";
sqlCommand.Parameters.AddWithValue("@USER_ID", userId);
sqlCommand.Parameters.AddWithValue("@HAS_POSITION", hasPosition);
sqlCommand.Parameters.AddWithValue("@HAS_SCHOOL_TAGS", hasEducationsTags);
///-- not using OUTPUT parameter but by getting the return result
SqlParameter sqlParameter = sqlCommand.Parameters.Add("@RESULT", SqlDbType.Decimal);
sqlParameter.Direction = ParameterDirection.ReturnValue;
sqlCommand.ExecuteNonQuery();
completeness = Convert.ToDecimal(sqlParameter.Value);
}
}
return completeness;
}
我检查过的一些链接:
- https://msdn.microsoft.com/en-us/library/system.threading.tasks.paralleloptions.maxdegreeofparallelism.aspx
- https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx
如何优化此逻辑以获得最佳时间?
如果您选择投反对票,请解释
谢谢。
由于您的代码是 I/O-bound(不是 CPU 绑定的),因此在问题上投入更多线程(或并行性)无济于事。你要的是异步并发(Task.WhenAll
),而不是并行并发(Parallel
):
using (var ctx = new MyContext())
{
var users = ctx.Users.Where(u => u.Status != -1);
var tasks = users.Select(UpdateUserProfilePercentageAsync);
await Task.WhenAll(tasks);
await ctx.SaveChangesAsync();
}
private async Task UpdateUserProfilePercentageAsync(User user)
{
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id);
}
您可能会发现您的数据库或其他服务不喜欢受到如此严厉的打击,在这种情况下您可以进行一些节流。例如,一次 20 个:
private SemaphoreSlim _throttle = new SemaphoreSlim(20);
private async Task UpdateUserProfilePercentageAsync(User user)
{
await _throttle.WaitAsync();
try
{
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id);
}
finally { _throttle.Release(); }
}
我正在尝试缩短操作的执行时间。
我得到了一个用户列表,通过用户 ID,我正在执行一个服务计算一些数据的方法。在我通过用户 ID 获取数据后,我将它设置到每个实体,而不是仅仅保存上下文。
目前使用这个简单的代码大约需要 1 小时。每 100 个用户大约 21 - 24 秒。但这取决于每个用户。
using (var ctx = new MyContext())
{
var users = ctx.Users.Where(u => u.Status != -1).ToList();
foreach (var user in users)
{
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id);
}
await ctx.SaveChangesAsync();
}
我也这样试过,但需要更长的时间:
Parallel.ForEach(
users,
new ParallelOptions { MaxDegreeOfParallelism = -1 },
user => {
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id); }
);
**计算百分比**
public async Task<decimal> CalculatePercentage (int userId)
{
decimal completeness = 0;
bool? hasEducationsTags = null; //--> nullable boolea will handle 3 cases on SP side
bool? hasPosition = null; //--> nullable boolea will handle 3 cases on SP side
///-- check if user has any education
if(await SchoolService.HasEducations(userId).ConfigureAwait(false))
{
hasEducationsTags = await SchoolService.HasAnyFieldsOfStudy(user=>user.Id == userId);
}
///-- check if user can set position
if (await UserInfoService.CanSetLocation(userId).ConfigureAwait(false))
{
///-- check if position was set
string accountName = await UserInfoService.GetAccountName(userId).ConfigureAwait(false);
hasPosition = await HasUserSetPosition(accountName).ConfigureAwait(false);
}
///-- call store procedure
using (SqlConnection sqlConnection = ConnectionFactory.Create())
{
await sqlConnection.OpenAsync().ConfigureAwait(false);
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandType = CommandType.StoredProcedure;
sqlCommand.CommandText = "ProfileCompletionAlgorithm";
sqlCommand.Parameters.AddWithValue("@USER_ID", userId);
sqlCommand.Parameters.AddWithValue("@HAS_POSITION", hasPosition);
sqlCommand.Parameters.AddWithValue("@HAS_SCHOOL_TAGS", hasEducationsTags);
///-- not using OUTPUT parameter but by getting the return result
SqlParameter sqlParameter = sqlCommand.Parameters.Add("@RESULT", SqlDbType.Decimal);
sqlParameter.Direction = ParameterDirection.ReturnValue;
sqlCommand.ExecuteNonQuery();
completeness = Convert.ToDecimal(sqlParameter.Value);
}
}
return completeness;
}
我检查过的一些链接:
- https://msdn.microsoft.com/en-us/library/system.threading.tasks.paralleloptions.maxdegreeofparallelism.aspx
- https://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx
如何优化此逻辑以获得最佳时间?
如果您选择投反对票,请解释
谢谢。
由于您的代码是 I/O-bound(不是 CPU 绑定的),因此在问题上投入更多线程(或并行性)无济于事。你要的是异步并发(Task.WhenAll
),而不是并行并发(Parallel
):
using (var ctx = new MyContext())
{
var users = ctx.Users.Where(u => u.Status != -1);
var tasks = users.Select(UpdateUserProfilePercentageAsync);
await Task.WhenAll(tasks);
await ctx.SaveChangesAsync();
}
private async Task UpdateUserProfilePercentageAsync(User user)
{
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id);
}
您可能会发现您的数据库或其他服务不喜欢受到如此严厉的打击,在这种情况下您可以进行一些节流。例如,一次 20 个:
private SemaphoreSlim _throttle = new SemaphoreSlim(20);
private async Task UpdateUserProfilePercentageAsync(User user)
{
await _throttle.WaitAsync();
try
{
user.ProfilePercentage = await UserAlgorithm.CalculatePercentage(user.Id);
}
finally { _throttle.Release(); }
}