Почему синхронизированная оболочка для ArrayList не работает? - PullRequest
4 голосов
/ 12 февраля 2011

Я сгенерировал прокси-классы для веб-службы в Visual Studio с помощью «Добавить веб-ссылку». Сгенерированный класс RTWebService имеет метод SetValueAsync. Я расширил этот класс и добавил SetValueRequest, который отслеживает запросы и отменяет все ожидающие запросы при возникновении ошибки. С каждым запросом я сохраняю объект userState в ArrayList, который я создал следующим образом:

requests = ArrayList.Synchronized(new ArrayList());

Я создал метод:

public void CancelPendingRequests() {
  lock (requests.SyncRoot) {
    if (requests.Count > 0) {
      foreach (object request in requests) {
        this.CancelAsync(request);
      }
      requests.Clear();
    }
  }
}

Я вызываю этот метод, когда запрос возвращается по событию SetValueCompleted:

private void onRequestComplete(
    object sender, 
    Service.SetValueCompletedEventArgs args
) {
  lock (syncResponse) {
    if (args.Cancelled) {
      return;
    }

    if (args.UserState != null) {
      requests.Remove(args.UserState);
    }

    if (args.Error != null) {
      CancelPendingRequests();
    }
  }
}

Чтобы начать новый запрос, я звоню:

public void SetValueRequest(string tag, string value) {
  var request = new object();
  this.SetValueAsync(tag, value, request);
  requests.Add(request);
}

Каждый раз, когда я делаю запрос, и в то же время возвращается ответ с ошибкой, я получаю TargetInvocationException в CancelPendingRequests. Внутреннее исключение - InvalidOperationException для ArrayList в методе CancelPendingRequests, говорящее:

Коллекция была изменена; операция перечисления может не выполняться.

Похоже, SetValueRequest изменил объект requests, пока я его перечислял. Я думал, что это невозможно, потому что я использовал синхронизированную оболочку для ArrayList и использую SyncRoot для синхронизации перечисления. Я немного застрял в этом, так что если у кого-нибудь есть идея?

Ответы [ 3 ]

3 голосов
/ 12 февраля 2011
  1. никогда не используйте SyncRoot, он по своей сути сломан.(если вы делитесь списком, вы просто приглашаете в тупик)

  2. Не используйте ArrayList, он должен быть помечен как «Устаревший».

  3. ArrayList.Synchronized return - это то, что работает медленнее, но не поточно-ориентировано, то есть не потоково-безопасно во время набора операций.

  4. вы можете либо использовать что-то из System.Collection.Concurrent, либо использовать ReaderWriterLockSlim

2 голосов
/ 12 февраля 2011

ОРИГИНАЛЬНЫЙ ОТВЕТ

Я решил проблему, удалив перечисление.Теперь я использую:

public void CancelPendingRequests() {
  lock (requests.SyncRoot) {
    if (requests.Count > 0) {
      for (int i = 0; i < requests.Count; i++) {
        this.CancelAsync(requests[i]);
      }
      requests.Clear();
    }
  }
}

Кажется, это помогает.Я все еще немного волнуюсь, что это lock (requests.SyncRoot) не сработало на перечислении, так почему бы это сработало здесь?Во всяком случае, теперь я не могу воспроизвести исключение, как раньше, поэтому я считаю эту проблему решенной.Я не могу больше тратить время на это.

РЕДАКТИРОВАТЬ

Забудьте мой глупый ответ выше.Я работал над проектом и должен был добиться прогресса.Теперь я обнаружил проблему:

Так что оказалось, что эта ошибка вообще не связана с многопоточностью.Весь код был выполнен в одном потоке, мне не нужны были эти блокировки.Проблема заключается в том, что я отменял запросы в своем перечислении.Метод CancelAsync вызывает событие SetValueCompleted, которое, в свою очередь, вызывает requests.Remove, тем самым изменяя запросы внутри перечисления.Сегодня я узнал некоторую ловушку с событиями.

Я решил проблему, перечислив локальную копию объекта requests, который я создал с помощью метода ToArray.

public void CancelPendingRequests() 
  if (requests.Count > 0) {
    for (object request in requests.ToArray()) {
      this.CancelAsync(request);
    }
  }
}
0 голосов
/ 12 февраля 2011

Попытка добавления локальной переменной в ваш метод CancelPendingRequests для каждого объекта запроса, например:

  public void CancelPendingRequests() {
  lock (requests.SyncRoot) 
  {
      if (requests.Count > 0) 
      {
          foreach (object request in requests) 
          {
          object currentRequest = request; //Add this
          this.CancelAsync(currentRequest);
          }
          requests.Clear();
       }
  }

}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...