多线程任务读取列表,导致索引超出范围

Bes*_*Ley 2 .net c# multithreading task task-parallel-library

存在这样的情况:应用系统需要每1分钟向在线用户发布消息,并且代码使用多头任务来读取消息列表.但程序运行不正常,会抛出一个超出范围异常的索引.请有人可以提出任何建议,谢谢.

private Timer taskTimer;
private static readonly object _locker = new object();
private static IList<Message> _messages = null;

private void OnTimerElapsed(object sender)
{
    var msgModel = new MessageModel();
    _messages = msgModel.GetMessageList();
    var msgCount = _messages.Count();

    Task[] _tasks = new Task[msgCount];
    for (int i = 0; i < msgCount; i++)
    {
        if (i < msgCount)
        {
            _tasks[i] = Task.Factory.StartNew(() =>
            {
                lock (_locker)
                {
                    PushMessage(i);
                }
            });
        }
    }

    //waiting all task finished
    while (_tasks.Any(t => !t.IsCompleted)) { }
}

private void PushMessage(int i)
{          
    var msg = _messages[i];         //it will throw an exception here...
    //send message to on line users.
    SendToOnlineUsers(msg);
}

Error:
Index was out of range. Must be non-negative and less than the size of the collection.

StackTrace Details:
   at System.ThrowHelper.ThrowArgumentOutOfRangeException()
   at System.Collections.Generic.List`1.get_Item(Int32 index)
   at WebIM.Hubs.BackgroudPushServiceTimer.PushMessage(Int32 i) in ...
   at WebIM.Hubs.BackgroudPushServiceTimer.<>c__DisplayClass6.<OnTimerElapsed>b__2() in ...
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()
Run Code Online (Sandbox Code Playgroud)

如果消息计数为4,并且在PushMessage函数中索引也将为4,则它超出范围.

Jon*_*eet 5

问题是你正在捕获i一个值随时间变化的变量.你可以通过在循环中制作本地副本来修复它:

for (int i = 0; i < msgCount; i++)
{
    int copyOfI = i;
    if (i < msgCount)
    {
        _tasks[i] = Task.Factory.StartNew(() =>
        {
            lock (_locker)
            {
                PushMessage(copyOfI);
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

也就是说,我怀疑有更简洁的方法可以解决这个问题 - 特别是当你创建几个使用相同锁的任务时.你实际上并没有实现任何并发性,然后你在等待一切都完成 - 所以为什么你要解决所有这些麻烦呢?

您还应该将Task.WhenAll/ Task.WaitAll作为一种更有效的等待任务完成的方式.

编辑:如果您可以并行执行此操作,请考虑使用Parallel.ForParallel.ForEach替代.