2009-11-25 3 views
0

J'ai une fonction de blocage qui exécute une requête MySQL asynchrone et renvoie le résultat quand il est obtenu. La raison en est asynchrone est ce programme n'est pas autorisé à se bloquer lors d'une requête.Attente asynchrone lorsque la fonction C# est en cours d'exécution

La fonction est appelée lorsque l'utilisateur appuie sur un bouton, de sorte que la fonction peut être appelée plusieurs fois avant la fin de la première requête. J'ai pensé que je pourrais ajouter un booléen pour vérifier si une requête est en train d'être exécutée et que la fonction attende que cela soit fait avant de continuer, mais cela ne fonctionne pas comme prévu. Il y a un problème avec les deux DoEvents() que j'utilise. Si vous commentez l'une des deux, cela fonctionne très bien, sauf que l'interface utilisateur se fige.

Comment faire en sorte que la fonction effectue une attente non bloquante pendant l'exécution d'une requête, ainsi qu'une attente non bloquante pendant la récupération de la requête elle-même? Je préférerais vraiment garder ceci sur un thread, car la fonction elle-même bloque le code qui l'a appelée. Toute aide serait grandement appréciée!

public Exception LastError; 
    public MySqlConnection Conn; 
    public MySqlDataReader Reader; 
    public bool IsExecuting = false; 

    public MySqlDataReader MySQL_Query(string Query, [Optional] params string[] Values) 
    { 
     while (IsExecuting) 
     { 
      System.Windows.Forms.Application.DoEvents(); 
      System.Threading.Thread.Sleep(20); 
     } 

     if (IsConnected() == false) 
      ConnectToDatabase(); 

     for (int i = 0; i < Values.Length; i++) 
      Values[i] = MySQL_SafeValue(Values[i]); 
     if (Reader != null && Reader.IsClosed == false) 
      Reader.Close(); 

     IsExecuting = true; 
     try 
     { 
      MySqlCommand Cmd = new MySqlCommand(String.Format(Query, Values), Conn); 
      IAsyncResult aRes = Cmd.BeginExecuteReader(); 
      while (!aRes.IsCompleted) 
      { 
       System.Windows.Forms.Application.DoEvents(); 
       System.Threading.Thread.Sleep(20); 
      } 
      Reader = Cmd.EndExecuteReader(aRes); 
      IsExecuting = false; 
     } 
     catch (Exception e) 
     { 
      IsExecuting = false; 
      LastError = e; 
      return null; 
     } 

     return Reader; 
    } 

Répondre

3

Il existe de nombreuses façons d'effectuer un travail asynchrone, de l'utilisation du pool de threads directement aux helpers comme BackgroundWorker. Toutefois, cela ne répond pas à votre question principale, ce qui est un peu contradictoire, c'est-à-dire que vous souhaitez effectuer une attente non bloquante. Je suggère que vous ne bloquez pas du tout ce qui est, si vous exécutez déjà alors ignorez la demande et ne faites rien. Vous voudrez peut-être donner votre avis pour dire "déjà travaillé" dans cette situation.

Maintenant aux problèmes réels avec votre code. Comme Adam l'a noté, vous ne devriez vraiment pas utiliser DoEvents et Sleep. Au lieu de cela, vous publiez l'élément de travail en cours d'exécution dans une tâche d'arrière-plan et utilisez un indicateur pour synchroniser entre le thread d'interface utilisateur et le thread exécutant votre tâche, par exemple.

/// <summary> 
    /// Used to prevent more than one worker. 
    /// </summary> 
    private bool working = false; 

    /// <summary> 
    /// Must use a lock to synch between UI thread and worker thread. 
    /// </summary> 
    private object stateLock = new object(); 

    /// <summary> 
    /// Used to pass custom args into the worker function. 
    /// </summary> 
    private class Data 
    { 
     public string query; 
     public string[] values; 
    } 

    /// <summary> 
    /// Called in your UI thread in response to button press. 
    /// </summary> 
    /// <param name="Query"></param> 
    /// <param name="Values"></param> 
    public void UiRequestToDoWork(string Query, params string[] Values) 
    { 
     lock (stateLock) 
     { 
      if (working) 
      { 
       // Do nothing! 
       Trace.WriteLine("Already working!"); 
      } 
      else 
      { 
       var backgroundWorker = new System.ComponentModel.BackgroundWorker(); 
       backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(backgroundWorker_DoWork); 
       backgroundWorker.RunWorkerAsync(new Data { query = Query, values = Values }); 
       this.working = true; 
      } 
     } 
    } 

    /// <summary> 
    /// Does all the background work. 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) 
    { 
     try 
     { 
      Data data = e.Argument as Data; 
      if (data != null) 
      { 
       // Do your query in here - just simulating work with a sleep. 
       Trace.WriteLine("Working..."); 
       System.Threading.Thread.Sleep(500); 

       // Note: you can't access the UI directly here in the worker thread. Use 
       // Form.Invoke() instead to update the UI after your work is done. 
      } 
     } 
     finally 
     { 
      // Note the use of finally to be safe if exceptions get thrown. 
      lock (stateLock) 
      { 
       this.working = false; 
      } 
      Trace.WriteLine("Finished!"); 
     } 
    } 
6

Vous ne devriez pas utiliser DoEvents et Sleep pour créer une interface utilisateur réactive. Pour effectuer des opérations asynchrones dans l'interface utilisateur, voir la classe BackgroundWorker.

1

Bien qu'il n'a pas été une option quand vous avez posé votre question, si vous pouvez passer à 4,5 .NET, il est maintenant beaucoup plus propre façon de les opérations asynchrones pendant l'écriture encore essentiellement de la même façon que vous le feriez pour code synchrone. Cela implique l'utilisation des nouveaux mots-clés async et await.

Voir:
An Async Primer pour une introduction aux nouvelles fonctionnalités
Et here est une question SO référencement spécifiquement les connexions MySQL.

Questions connexes