Почему это вызывает ArgumentOutOfRangeException при использовании Parallel.For?
Я попытался написать что-нибудь для хэширования и сравнить их со списком, чтобы увидеть, существует ли соответствующий хеш или нет.
Я получил это работает нормально, используя цикл 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 вопроса:
- Почему я получаю исключение?
- Правильно ли я использую BlockingCollection для того, чего я пытаюсь достичь?
1 ответ
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();