Почему это вызывает ArgumentOutOfRangeException при использовании Parallel.For?

1

У меня было желание написать что-то для хэш-номеров и проверить их на список, чтобы увидеть, существует ли соответствующий хэш или нет.

Я получил эту работу отлично, используя цикл for. Затем я решил попробовать ускорить работу с помощью Parallel.For - к сожалению, это вызывает исключение ArgumentOutOfRangeException, что у меня проблема с отладкой.

public class HashCompare
{
    private string encryptedCardNumber;
    private Encrypter encrypter;
    private BlockingCollection<string> paraCardNumberList;

    public string Compare(string hash)
    {
        bool exists = Lookup.hashExists(hash);

        if (exists)
        {
            string unencryptedCardNumber = Lookup.GetUnencryptedCardNumber(hash);
            return unencryptedCardNumber;
        }
        return null;
    }

        public BlockingCollection<string> PLCompareAll()
    {

        paraCardNumberList = new BlockingCollection<string>();

        Parallel.For(100000, 999999, i =>               
        {
            encrypter = new Encrypter();

            encryptedCardNumber = encrypter.EncryptCardNumber(i.ToString());
            var result = Compare(encryptedCardNumber);

            if (result != null)
            {
                paraCardNumberList.Add(result);
            }
        });
        paraCardNumberList.CompleteAdding();

        return paraCardNumberList;
    }
}

Ошибка возникает случайно при вызове encrypter.EncryptCardNumber (похоже, на returnValue.ToString())

private StringBuilder returnValue

public string EncryptCardNumber(string str)
{
    try
    {
        var sha1 = SHA1.Create();
        byte[] hashData = sha1.ComputeHash(Encoding.Default.GetBytes(str));
        returnValue = new StringBuilder();

        for (int i = 0; i < hashData.Length; i++)
        {
            returnValue.Append(hashData[i].ToString("x2"));
        }
    }
    catch (Exception ex)
    {
        string strerr = "Error in hash code: " + ex.Message;
    }
    return returnValue.ToString();
}

У меня есть 2 вопроса:

  1. Почему я получаю исключение?
  2. Могу ли я использовать BlockingCollection для того, чего я пытаюсь достичь?
  • 0
    Ваш код не компилируется. Пожалуйста, отправьте реальный код.
  • 0
    Отредактировано - надеюсь, что все в порядке!
Показать ещё 1 комментарий
Теги:
multithreading
blockingcollection
parallel.for

1 ответ

2
Лучший ответ

StringBuilder не является потокобезопасным:

Любые члены экземпляра не гарантируют безопасность потоков.

Но, похоже, вы используете один и тот же экземпляр StringBuilder для всех ваших вызовов EncryptCardNumber. Не удивительно, что вы столкнулись с состоянием гонки, когда один поток выполняет .ToString() а другой пытается добавить больше символов.

Я предполагаю, что вы на самом деле не собираетесь переписывать все эти потоки и добавлять друг к другу экземпляры StringBuilder. Попробуйте объявить объект как локальную переменную, а не поле этого класса. Тот же принцип, вероятно, применим и к другим переменным, таким как encrypter.

Что касается вашего BlockingCollection<>, это, вероятно, прекрасный подход, но я бы лично пошел с чем-то более функциональным:

return Enumerable.Range(100000, 999999)
    .AsParallel() // One line to make this parallel
    .Select(i => new Encrypter().EncryptCardNumber(i.ToString())
    .Select(Compare)
    .Where(hash => hash != null)
    .ToList();
  • 1
    Фантастика, большое спасибо. Действительно хорошо объясненный, я сделал изменение StringBuilder, и оно работает как шарм, теперь я буду воплощать ваши другие предложения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню