Поможет ли параллелизм повысить производительность заблокированного объекта, должен ли он работать в однопоточном режиме или есть другой метод?
Я заметил, что при доступе к набору данных и добавлении строк из нескольких потоков были выброшены исключения. Поэтому я создал «поточно-ориентированную» версию для добавления строк путем блокировки таблицы перед обновлением строки. Эта реализация работает, но при большом количестве транзакций она кажется медленной.
public partial class HaMmeRffl
{
public partial class PlayerStatsDataTable
{
public void AddPlayerStatsRow(int PlayerID, int Year, int StatEnum, int Value, DateTime Timestamp)
{
lock (TeamMemberData.Dataset.PlayerStats)
{
HaMmeRffl.PlayerStatsRow testrow = TeamMemberData.Dataset.PlayerStats.FindByPlayerIDYearStatEnum(PlayerID, Year, StatEnum);
if (testrow == null)
{
HaMmeRffl.PlayerStatsRow newRow = TeamMemberData.Dataset.PlayerStats.NewPlayerStatsRow();
newRow.PlayerID = PlayerID;
newRow.Year = Year;
newRow.StatEnum = StatEnum;
newRow.Value = Value;
newRow.Timestamp = Timestamp;
TeamMemberData.Dataset.PlayerStats.AddPlayerStatsRow(newRow);
}
else
{
testrow.Value = Value;
testrow.Timestamp = Timestamp;
}
}
}
}
}
Теперь я могу безопасно вызывать это из нескольких потоков, но действительно ли это мне что-нибудь дает? Могу ли я сделать это по-другому для повышения производительности. Например, есть ли способ использовать пространство имен System.Collections.Concurrent
для оптимизации производительности или какие-либо другие методы?
Кроме того, я обновляю базовую базу данных после обновления всего набора данных, а это занимает очень много времени. Будет ли это считаться операцией ввода-вывода и стоит ли использовать параллельную обработку, обновляя ее после обновления каждой строки в наборе данных (или некоторого количества строк).
ОБНОВЛЕНИЕ
Я написал код для тестирования одновременной и последовательной обработки, который показывает, что параллельная обработка занимает примерно 30% больше времени, и здесь я должен использовать последовательную обработку. Я предполагаю, что это связано с тем, что из-за блокировки базы данных накладные расходы на ConcurrentQueue
становятся более дорогостоящими, чем выгоды от параллельной обработки. Правильно ли это заключение и могу ли я что-нибудь сделать, чтобы ускорить обработку? или я застрял в отношении Datatable
«Вы должны синхронизировать любые операции записи».
Вот мой тестовый код, который может быть неверным с научной точки зрения. Вот таймер и звонки между ними.
dbTimer.Restart();
Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRow = InsertToPlayerQ(addUpdatePlayers);
Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRow = InsertToPlayerStatQ(addUpdatePlayers);
UpdatePlayerStatsInDB(addPlayerRow, addPlayerStatRow);
dbTimer.Stop();
System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds single threaded", dbTimer.Elapsed.TotalSeconds);
dbTimer.Restart();
ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows = InsertToPlayerQueue(addUpdatePlayers);
ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows = InsertToPlayerStatQueue(addUpdatePlayers);
UpdatePlayerStatsInDB(addPlayerRows, addPlayerStatRows);
dbTimer.Stop();
System.Diagnostics.Debug.Print("Writing to the dataset took {0} seconds concurrently", dbTimer.Elapsed.TotalSeconds);
В обоих примерах я одинаково добавляю к Queue
и ConcurrentQueue
однопоточные. Единственное отличие - это вставка в datatable. Однопоточный подход включает следующие вставки:
private static void UpdatePlayerStatsInDB(Queue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, Queue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
{
try
{
HaMmeRffl.PlayersRow.PlayerValue row;
while (addPlayerRows.Count > 0)
{
row = addPlayerRows.Dequeue();
TeamMemberData.Dataset.Players.AddPlayersRow(
row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
row.Active, row.NewsUpdate);
}
}
catch (Exception)
{
TeamMemberData.Dataset.Players.RejectChanges();
}
try
{
HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
while (addPlayerStatRows.Count > 0)
{
row = addPlayerStatRows.Dequeue();
TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
}
}
catch (Exception)
{
TeamMemberData.Dataset.PlayerStats.RejectChanges();
}
TeamMemberData.Dataset.Players.AcceptChanges();
TeamMemberData.Dataset.PlayerStats.AcceptChanges();
}
Конкурент добавляет следующее
private static void UpdatePlayerStatsInDB(ConcurrentQueue<HaMmeRffl.PlayersRow.PlayerValue> addPlayerRows, ConcurrentQueue<HaMmeRffl.PlayerStatsRow.PlayerStatValue> addPlayerStatRows)
{
Action actionPlayer = () =>
{
HaMmeRffl.PlayersRow.PlayerValue row;
while (addPlayerRows.TryDequeue(out row))
{
TeamMemberData.Dataset.Players.AddPlayersRow(
row.PlayerID, row.Name, row.PosEnum, row.DepthEnum,
row.TeamID, row.RosterTimestamp, row.DepthTimestamp,
row.Active, row.NewsUpdate);
}
};
Action actionPlayerStat = () =>
{
HaMmeRffl.PlayerStatsRow.PlayerStatValue row;
while (addPlayerStatRows.TryDequeue(out row))
{
TeamMemberData.Dataset.PlayerStats.AddUpdatePlayerStatsRow(
row.PlayerID, row.Year, row.StatEnum, row.Value, row.Timestamp);
}
};
Action[] actions = new Action[Environment.ProcessorCount * 2];
for (int i = 0; i < Environment.ProcessorCount; i++)
{
actions[i * 2] = actionPlayer;
actions[i * 2 + 1] = actionPlayerStat;
}
try
{
// Start ProcessorCount concurrent consuming actions.
Parallel.Invoke(actions);
}
catch (Exception)
{
TeamMemberData.Dataset.Players.RejectChanges();
TeamMemberData.Dataset.PlayerStats.RejectChanges();
}
TeamMemberData.Dataset.Players.AcceptChanges();
TeamMemberData.Dataset.PlayerStats.AcceptChanges();
}
Разница во времени составляет 4,6 секунды для однопоточного и 6,1 для параллельного вызова.
ConcurrentQueue
и собирался попытаться использовать несколько потоков, чтобы попытаться ускорить транзакции базы данных, но потом мне было интересно, следует ли мне просто использовать обычную очередь и один поток - person Harrison   schedule 27.09.2013AddPlayerStatsRow
). Самый простой способ сделать это - BlockingCollection, поддерживаемыйConcurrentQueue
запущенным в своем собственном потоке (вы просто вызываетеGetConsumingEnumerable()
в цикле foreach и позволяете ему работать вечно). Более сложные решения могут включать написание собственного расширения для _4 _. - person Scott Chamberlain   schedule 27.09.2013