2015-07-16 3 views
1

Я работаю над сценарием автозаполнения.Остановить поток, работающий с DB

Когда пользователь перестает печатать, его ввод отправляется на сервер, который будет искать некоторые совпадения в БД.

Например: При поиске людей «Обам» должен вернуться «Барак Обама».

Я хочу, чтобы этот поиск ограничивался ~ 500 мс. Если это займет больше времени, я хочу прервать поиск и вернуть результаты только вовремя.

Я начинаю с поиска идеального соответствия (это не будет прервана), то я ищу частичного совпадения (это один может быть прерван):

private static MySqlConnection conn = new MySqlConnection(ConfigurationManager.AppSettings["CxMySql"].ToString()); 

protected void Page_Load(object sender, EventArgs e) 
{ 
    conn.Open(); 
    SearchTerm(table,term,domaine); 
    conn.Close(); // TimeoutException HERE 
    conn.Dispose(); 
} 

private void SearchTerm(string table,string term,string domaine) 
{ 
    Dictionary<int, Scoring> results = new Dictionary<int, Scoring>(); 

    var requete = "SELECT m.id, m.nom as label FROM marque m WHERE m.nom = '" + term + "'"; 
    var Cmd = new MySqlCommand(requete, conn); 
    using (var Rd = Cmd.ExecuteReader()) 
    { 
     while (Rd.Read()) 
     { 
      results.Add(int.Parse(Rd["id"].ToString()), new Scoring() 
      { 
       score = 1000, 
       value = Rd["label"].ToString() 
      }); 
     } 
    } 
    // Here it should be 500, but I put 1 to force troubles to appear. 
    RunWithTimeout(() => FindOtherBrands(term, ref results), TimeSpan.FromMilliseconds(1)); 

    var resultsList = results.ToList(); 
    resultsList.Sort(
     delegate(KeyValuePair<int, Scoring> firstPair, 
      KeyValuePair<int, Scoring> nextPair) 
     { 
      return nextPair.Value.score - firstPair.Value.score; 
     } 
    ); 
} 


private void FindOtherBrands(string term, ref Dictionary<int, Scoring> results) 
{ 
    MySqlCommand Cmd; 
    string requete; 
    requete = "SELECT m.id, m.nom as label FROM marque m WHERE m.nom LIKE '" + term + "%'"; 
    Cmd = new MySqlCommand(requete, conn); 
    var Rd = Cmd.ExecuteReader(); // NullReferenceException HERE 
    while (Rd != null && Rd.Read()) 
    { 
     int id = int.Parse(Rd["id"].ToString()); 
     if (!results.ContainsKey(id)) 
     { 
      results.Add(id, new Scoring() 
      { 
       score = 100, 
       value = Rd["label"].ToString() 
      }); 
     } 
    } 
    Rd.Close(); 
    requete = "SELECT m.id, m.nom as label FROM marque m WHERE m.nom LIKE '%" + term + "%'"; 
    Cmd = new MySqlCommand(requete, conn); 
    Rd = Cmd.ExecuteReader(); 
    while (Rd != null && Rd.Read()) 
    { 
     int id = int.Parse(Rd["id"].ToString()); 
     if (!results.ContainsKey(id)) 
     { 
      results.Add(id, new Scoring() 
      { 
       score = 10, 
       value = Rd["label"].ToString() 
      }); 
     } 
    } 
    Rd.Close(); 
} 
  • Я нашел RunWithTimeout метод здесь: stop executing code in thread after 30s

    bool RunWithTimeout(ThreadStart threadStart, TimeSpan timeout) 
    { 
        Thread workerThread = new Thread(threadStart); 
    
        workerThread.Start(); 
    
        bool finished = true; 
        if (!workerThread.Join(timeout)) 
        { 
         workerThread.Abort(); 
         finished = false; 
        } 
    
    
        return finished; 
    } 
    
  • Скоринг является структурой для легкой сортировки результатов

    private struct Scoring 
    { 
        public string value; 
        public int score; 
    } 
    

Цель состоит в том, чтобы иметь результаты (не обязательно все) быстро.

ПРОБЛЕМЫ
  • я случайно получить TimeoutException после ~ 30s на conn.Close(); линии.
  • Я случайно получаю NullReferenceException при первом Cmd.ExecuteReader(); звоните в FindOtherBrands.

Может ли кто-нибудь объяснить мне, почему? Я делаю что-то неправильно или есть обходной путь?

Я думаю, что исключение TimeoutException связано с тем, что я пытаюсь закрыть соединение во время выполнения команды, могу ли я отбросить/отменить этот запрос?

+0

С какой версией .NET вы работаете? –

+0

Было бы разумно закрыть ресурсы базы данных, прежде чем прерывать поток. – HashPsi

+0

К сожалению, в базах данных нет способа сказать все результаты, которые вы можете получить в течение определенного периода времени.Вы можете запросить все результаты, но нет гарантии, что база данных начнет отправлять их вам быстро, как только они их обнаружат. Единственный верный способ сделать это - запросить n-й результат в цикле и установить тайм-аут команды. Это крайне неэффективно. –

ответ

1

Я бы выбрал другой подход. Поскольку вы запрашиваете базу данных, которая, естественно, асинхронна, вы можете использовать async-await для запроса данных. При этом, вы можете передать CancellationToken, который устанавливается тайм-аута, который вы будете контролировать с каждым прочитанным:

Например:

private async Task FindOtherBrands(string term, 
            Dictionary<int, Scoring> results, 
            CancellationToken cancellationToken) 
{ 
    MySqlCommand cmd; 
    string requete; 
    requete = "SELECT m.id, m.nom as label 
       FROM marque m 
       WHERE m.nom LIKE '" + term + "%'"; 

    cmd = new MySqlCommand(requete, conn); 
    var Rd = await cmd.ExecuteReaderAsync(); 
    while (Rd != null && await Rd.ReadAsync()) 
    { 
     cancellationToken.ThrowIfCancellationRequested(); 

     int id = int.Parse(Rd["id"].ToString()); 
     if (!results.ContainsKey(id)) 
     { 
      results.Add(id, new Scoring() 
      { 
       score = 100, 
       value = Rd["label"].ToString() 
      }); 
     } 
    } 

    Rd.Close(); 
    requete = "SELECT m.id, m.nom as label 
       FROM marque m 
       WHERE m.nom LIKE '%" + term + "%'"; 

    cmd = new MySqlCommand(requete, conn); 
    Rd = await Cmd.ExecuteReaderAsync(); 
    while (Rd != null && await Rd.ReadAsync()) 
    { 
     cancellationToken.ThrowIfCancellationRequest(); 
     int id = int.Parse(Rd["id"].ToString()); 
     if (!results.ContainsKey(id)) 
     { 
      results.Add(id, new Scoring() 
      { 
       score = 10, 
       value = Rd["label"].ToString() 
      }); 
     } 
    } 
    Rd.Close(); 
} 

И когда вы вызываете его, все, что вам нужно, это обернуть это в try-catch и передать CancellationToken:

private async Task<bool> RunWithTimeoutAsync(TimeSpan timeout) 
{ 
    bool finished; 
    try 
    { 
     var cancellationTokenSource = new CancellationTokenSource(timeout); 
     await FindOtherBrandsAsnyc(term, 
       results, 
       cancellationTokenSource.CancellationToken); 
     finished = true; 
    } 
    catch (OperationCanceledException e) 
    { 
     // Handle 
    } 

    return finished; 
} 

примечание стороны - Ваш запрос склонен к SQL Injection. Вы не должны использовать конкатенацию строк. Вместо этого используйте параметры запроса.

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