2009-11-25 225 views
0

我有一個阻塞函數,它執行異步MySQL查詢並在獲得結果時返回結果。原因是異步的是這個程序在查詢過程中不允許鎖定。執行C#函數時異步等待

該函數在用戶按下按鈕時調用,因此可能會在第一次查詢完成之前多次調用該函數。我想我可以添加一個布爾值來檢查查詢是否正在執行,並讓函數在繼續之前等待,直到它完成,但它沒有按預期工作。我使用的兩個DoEvents()存在一些問題。如果你註釋掉任何一個,它就會運行得很好,除了UI凍結。

如何讓函數在執行查詢時執行非阻塞等待,以及在查詢本身被取回時執行非阻塞等待?我真的更願意將它保留在一個線程上,因爲函數本身阻塞了調用它的代碼。任何幫助將不勝感激!

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; 
    } 

回答

3

有很多方法可以做異步工作,從直接使用線程池到像BackgroundWorker這樣的助手。

但是,這並不能回答你的主要問題,這是一個有點矛盾的問題,即你想做一個無阻塞的等待。如果你已經在執行,我會建議你不要阻止,然後忽略這個請求並且什麼也不做。在這種情況下,您可能需要提供一些反饋意見以說明「已經在工作」。

現在到您的代碼的實際問題。正如亞當指出,你真的不應該使用DoEvents和睡眠。而是將長時間運行的工作項發佈到某個後臺任務,並使用一個標誌在UI線程和運行任務的線程之間進行同步,例如,

/// <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

你不應該使用DoEventsSleep打造一個負責任的UI。要在UI中執行異步操作,請參閱BackgroundWorker類。

1

雖然這不是一個選項,當你問你的問題,如果你可以升級到.NET 4.5現在是向異步操作更清潔的方式,而在你會爲同樣的方式本質上還是寫同步代碼。這涉及使用新的asyncawait關鍵字。

參見:
An Async Primer爲介紹新功能
here是一個SO問題特別引用MySQL連接。