2016-08-15 4 views
1

У меня небольшая проблема с моим приложением. У меня есть редактор базы данных, который иногда зависает, когда я пытаюсь обновить файл базы данных. Не каждый раз, но довольно часто, и каждый раз, когда это происходит прямо перед внесением каких-либо изменений в базу данных. Я решил, что это из-за того, что вы не используете многопоточность. В последнее время я только начал изучать программирование, и я потерял даже после прочтения нескольких многопоточных объяснений. Может ли кто-нибудь объяснить мне, как мне его реализовать в моем конкретном примере?Каков наилучший способ многопоточности?

 
    private void adjustStatsButton_Click(object sender, EventArgs e) 
    { 
      ReadWrite.AdjustStats(winnerInput.Text, loserInput.Text); 
      winnerInput.Text = ""; 
      loserInput.Text = ""; 
      Refresh(leaderboardBox); 
    } 

 
    public class ReadWrite 
    { 
     public static void AdjustStats(string winner, string loser) 
     { 
      SQLiteConnection dbConnection = new SQLiteConnection("Data Source = Leaderboards.sqlite; Version = 3");

string sql = "SELECT * FROM leaderboard WHERE name='" + winner + "'"; SQLiteCommand command = new SQLiteCommand(sql, dbConnection); dbConnection.Open(); SQLiteDataReader reader = command.ExecuteReader(); double wrating = Convert.ToDouble(reader["rating"]); int wmatches = Convert.ToInt32(reader["matches"]); int wwins = Convert.ToInt32(reader["wins"]); sql = "SELECT * FROM leaderboard WHERE name='" + loser + "'"; command = new SQLiteCommand(sql, dbConnection); reader = command.ExecuteReader(); double lrating = Convert.ToDouble(reader["rating"]); int lmatches = Convert.ToInt32(reader["matches"]); int lwins = Convert.ToInt32(reader["wins"]); int llosses = Convert.ToInt32(reader["losses"]); double RC = (1 - ((wrating - lrating)/200)) * 8; if (RC < 0) RC *= -1; if (RC < 4) RC = 4; else if (RC > 12) RC = 12; wmatches++; wwins++; lmatches++; llosses++; wrating += RC; if (wrating < 0) wrating = 0; lrating -= RC; if (lrating < 0) lrating = 0; double wwinrate = Convert.ToDouble(wwins)/wmatches; double lwinrate = Convert.ToDouble(lwins)/lmatches; sql = "UPDATE leaderboard SET rating=" + wrating + ", matches=" + wmatches + ", wins=" + wwins + ", winrate=" + wwinrate + " WHERE name='" + winner + "'"; command = new SQLiteCommand(sql, dbConnection); command.ExecuteNonQuery(); sql = "UPDATE leaderboard SET rating=" + lrating + ", matches=" + lmatches + ", losses=" + llosses + ", winrate=" + lwinrate + " WHERE name='" + loser + "'"; command = new SQLiteCommand(sql, dbConnection); command.ExecuteNonQuery(); dbConnection.Close(); } }
+0

Что 'Refresh (leaderboardBox); 'do? –

+0

Обратите внимание, будьте осторожны с SQL-инъекцией с именами параметров' победитель' и 'loser'. – smoksnes

+2

Во-первых, код, который вы предоставили, кажется, не очень тяжелый. Но кажется, что вы это делаете в GUI-потоке, которого вы должны избегать. Попробуйте использовать «BackgroundWorker», как указано здесь http://stackoverflow.com/questions/6365887/can-you-link-to-a-good-example-of-using- backgroundworker-without-placement-it-on-a – smoksnes

ответ

0

Проблема что вы выполняете запрос в потоке пользовательского интерфейса. Вот пример использования BackgroundWorker, сильно, вдохновленный от this example, и с использованием Arguments. Таким образом, он будет работать в отдельном потоке и не будет блокировать графический интерфейс.

// Class for passing arguments 
public class BqArguments 
{ 
    public string Winner {get;set} 
    public string Loser {get;set} 
} 

И ваша реализация:

BackgroundWorker _bw; // You need to initialize this somewhere. 

private void adjustStatsButton_Click(object sender, EventArgs e) 
{ 
    // Maybe this should be initialized in ctor. But for this example we do it here... 
    _bw = new BackgroundWorker(); 
    var arguments = new BqArguments 
    { 
     Winner = winnerInput.Text, 
     Loser = loserInput.Text 
    } 
    _bw.DoWork += bw_DoWork; 
    _bw.RunWorkerCompleted += bw_RunWorkerCompleted; 
    _bw.RunWorkerAsync(arguments);  
} 

private void bw_DoWork (object sender, DoWorkEventArgs e) 
{ 
    // Run your query in the background. 
    var arguments = e.Argument as BqArguments; 
    ReadWrite.AdjustStats(arguments.Winner, arguments.Loser); 
} 

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    // All done. Let's update the GUI. 
    winnerInput.Text = ""; 
    loserInput.Text = ""; 
    Refresh(leaderboardBox); 
} 
+0

Отлично работает, но мне пришлось изменить 'e.Arguments' в' bw_DoWork' на 'e.Argument', потому что, по-видимому, '' DoWorkEventArgs 'не содержит определения для «Аргументы» – Draxuss

+0

Да, я потратил немного времени, пытаясь понять, почему он не работает, но теперь все в порядке. Спасибо! – Draxuss

0

Вам просто нужно использовать асинхронные ждут ключевых слов в ваших C# код, и это в конечном итоге приведет Асинхронные вызовы к БД я внес изменения в свой код:

private async void adjustStatsButton_Click(object sender, EventArgs e) 
    { 
      await ReadWrite.AdjustStats(winnerInput.Text, loserInput.Text); 
      winnerInput.Text = ""; 
      loserInput.Text = ""; 
      Refresh(leaderboardBox); 
    } 


    public class ReadWrite 
     { 
      public static Task AdjustStats(string winner, string loser) 
      { 
    return Task.Run(() => 
    { 
     SQLiteConnection dbConnection = new SQLiteConnection("Data Source = Leaderboards.sqlite; Version = 3"); 


      string sql = "SELECT * FROM leaderboard WHERE name='" + winner + "'"; 
      SQLiteCommand command = new SQLiteCommand(sql, dbConnection); 
      dbConnection.Open(); 
      SQLiteDataReader reader = await command.ExecuteReaderAsync(); 


      double wrating = Convert.ToDouble(reader["rating"]); 
      int wmatches = Convert.ToInt32(reader["matches"]); 
      int wwins = Convert.ToInt32(reader["wins"]); 

      sql = "SELECT * FROM leaderboard WHERE name='" + loser + "'"; 
      command = new SQLiteCommand(sql, dbConnection); 
      reader = await command.ExecuteReaderAsync(); 

      double lrating = Convert.ToDouble(reader["rating"]); 
      int lmatches = Convert.ToInt32(reader["matches"]); 
      int lwins = Convert.ToInt32(reader["wins"]); 
      int llosses = Convert.ToInt32(reader["losses"]); 

      double RC = (1 - ((wrating - lrating)/200)) * 8; 
      if (RC < 0) RC *= -1; 
      if (RC < 4) RC = 4; 
      else if (RC > 12) RC = 12; 

      wmatches++; 
      wwins++; 
      lmatches++; 
      llosses++; 
      wrating += RC; 
      if (wrating < 0) wrating = 0; 
      lrating -= RC; 
      if (lrating < 0) lrating = 0; 
      double wwinrate = Convert.ToDouble(wwins)/wmatches; 
      double lwinrate = Convert.ToDouble(lwins)/lmatches; 

      sql = "UPDATE leaderboard SET rating=" + wrating + ", matches=" + wmatches + ", wins=" + wwins + ", winrate=" + wwinrate + " WHERE name='" + winner + "'"; 
      command = new SQLiteCommand(sql, dbConnection); 
      await command.ExecuteNonQueryAsync(); 

      sql = "UPDATE leaderboard SET rating=" + lrating + ", matches=" + lmatches + ", losses=" + llosses + ", winrate=" + lwinrate + " WHERE name='" + loser + "'"; 
      command = new SQLiteCommand(sql, dbConnection); 
      await command.ExecuteNonQueryAsync(); 
      dbConnection.Close(); 
      } 
}); 

     } 
+0

Это один поток/задание, которое вы использовали. Вы можете делать выигрыш и проигрывать на отдельных потоках с ожиданием третьего потока, который выполняет остальную часть кода, т. Е. Из 'double wrating = Convert.ToDouble' – Neil

+0

Я не использовал этот подход, потому что одного потока было бы достаточно для него чтобы ваш пользовательский интерфейс реагировал на пользователя – Lakhtey

+0

@ Draxuss это ответ Лахтей не мой. :) – Neil

0

Ядро вашей проблемы является то, что вы вызываете блокирующие вызовы (синхронные) базы данных на потоке пользовательского интерфейса. Это блокирует ваш поток пользовательского интерфейса, пока он ждет базы данных, вместо того, чтобы держать его хорошо реагирующим.

В общем случае, так как они являются I операциями O-Based /, вы должны быть в состоянии сделать их естественно асинхронными, согласно Lakhtey's answer. Однако, SQLite не поддерживает фактические асинхронные операции. :(

Таким образом, в этом случае, лучше всего, чтобы просто обернуть работу базы данных в фоновом потоке Обратите внимание, что использование Task.Run намного превосходит BackgroundWorker:.

private async void adjustStatsButton_Click(object sender, EventArgs e) 
{ 
    await Task.Run(() => ReadWrite.AdjustStats(winnerInput.Text, loserInput.Text)); 
    winnerInput.Text = ""; 
    loserInput.Text = ""; 
    Refresh(leaderboardBox); 
} 
+0

Спасибо Стивен. здесь, когда вы говорите выше? – Zuzlx

+0

@Zuzlx: Для SQLite я бы использовал 'Task.Run'. –

+0

@StephenCleary, так что единственное, что мне нужно сделать, это добавить 'await Task.Run (() => [..])' и он будет автоматически обрабатывать его в другом потоке? Разве это не вызовет проблему с установкой 'winnerInput.Text' на' null'? – Draxuss

Смежные вопросы