2015-07-10 50 views
2

嘗試使用異步/等待和事務將多個記錄插入MySQL數據庫,但是它仍然會導致UI在循環中變爲凍結/無響應。使用事務進行批量插入會阻止異步進程上的UI

看下面的代碼,我在做什麼錯誤或如何實現,以便在此過程中UI仍然響應。

的異步方法

public static async Task AddRecords() { 
    foreach (string month in Months) { 
     await MakeTable(month); 
     string query="INSERT INTO `"+month+"` (Caller, Started, Dialed, DurationSec, DurationMin, Cost, Location, Switch) VALUES (@Caller, @Started, @Dialed, @DurationSec, @DurationMin, @Cost, @Location, @Switch);"; 
     using (MySqlConnection cn=new MySqlConnection(ConnectionString.ToString())) { 
      await cn.OpenAsync(); 
      using (MySqlTransaction trans=cn.BeginTransaction()) { 
       using (MySqlCommand cmd=new MySqlCommand(query, cn, trans)) { 
        cmd.CommandType=CommandType.Text; 
        foreach (Record r in CDR.Records) { 
         if (r.Started.ToString("yyyy-MM")==month) { 
          cmd.Parameters.Clear(); 
          cmd.Parameters.AddWithValue("@Caller", r.Caller); 
          cmd.Parameters.AddWithValue("@Started", r.Started); 
          cmd.Parameters.AddWithValue("@Dialed", r.Dialed); 
          cmd.Parameters.AddWithValue("@DurationSec", r.Duration); 
          cmd.Parameters.AddWithValue("@DurationMin", Math.Ceiling(r.Duration/60)); 
          cmd.Parameters.AddWithValue("@Cost", r.Cost); 
          cmd.Parameters.AddWithValue("@Location", r.Location); 
          cmd.Parameters.AddWithValue("@Switch", r.Switch.ToString()); 
          cmd.ExecuteNonQuery(); 
         } 
        } 
        trans.Commit(); 
       } 
      } 
      await cn.CloseAsync(); 
     } 
    } 
} 

上這是如何被稱爲一個片段:

private async void button1_Click(object sender, EventArgs e) { 
     this.Text = "Adding Records"; 
     await AddRecords(); 
     this.Text = "Completed"; 
    } 

順便說一句,該當UI阻塞,不應該它塊畢竟之前的代碼已經被執行。例如,在上面的按鈕點擊方法中,第一個'this.Text'沒有設置,因爲一旦await AddRecords();執行,它就發生在UI有機會完成更新之前,並且直到阻塞完成之後纔會完成這導致在UI級別僅注意到this.Text - "Completed"


UPDATE

的UI修改cmd.ExecuteNonQuery();await cmd.ExecuteNonQueryAsync();(由Yuval Itzchakov推薦),這使我相信它阻止在trans.Commit();線或某事與交易是怎麼做的後,仍然阻擋正在建設。

更新代碼

public static async Task AddRecords() { 
    foreach (string month in Months) { 
     await MakeTable(month); 
     string query="INSERT INTO `"+month+"` (Caller, Started, Dialed, DurationSec, DurationMin, Cost, Location, Switch) VALUES (@Caller, @Started, @Dialed, @DurationSec, @DurationMin, @Cost, @Location, @Switch);"; 
     using (MySqlConnection cn=new MySqlConnection(ConnectionString.ToString())) { 
      await cn.OpenAsync(); 
      using (MySqlTransaction trans=cn.BeginTransaction()) { 
       using (MySqlCommand cmd=new MySqlCommand(query, cn, trans)) { 
        cmd.CommandType=CommandType.Text; 
        foreach (Record r in CDR.Records) { 
         if (r.Started.ToString("yyyy-MM")==month) { 
          cmd.Parameters.Clear(); 
          cmd.Parameters.AddWithValue("@Caller", r.Caller); 
          cmd.Parameters.AddWithValue("@Started", r.Started); 
          cmd.Parameters.AddWithValue("@Dialed", r.Dialed); 
          cmd.Parameters.AddWithValue("@DurationSec", r.Duration); 
          cmd.Parameters.AddWithValue("@DurationMin", Math.Ceiling(r.Duration/60)); 
          cmd.Parameters.AddWithValue("@Cost", r.Cost); 
          cmd.Parameters.AddWithValue("@Location", r.Location); 
          cmd.Parameters.AddWithValue("@Switch", r.Switch.ToString()); 
          await cmd.ExecuteNonQueryAsync(); 
         } 
        } 
        trans.Commit(); 
       } 
      } 
      await cn.CloseAsync(); 
     } 
    } 
} 

回答

1

我覺得大部分時間都是在Commit的操作上花費的。問題是您的AddRecords函數中的每個await都會同步回調用該函數的UI線程。這是你的主要問題。

從此SynchronizationContext斷開連接導致您遇到問題的最簡單方法是強制AddRecordsThreadPool -Thread中運行。

這樣做很容易:

private async void button1_Click(object sender, EventArgs e) { 
    this.Text = "Adding Records"; 
    await Task.Run(() => AddRecords()); 
    this.Text = "Completed"; 
} 

這將在ThreadPool,只有你在這裏創造一次一切都做同步回UI的Task運行數據庫的東西。

這樣做的缺點是,您無法再使用AddRecords方法訪問UI。但是在你的代碼中你並沒有這樣做,所以我猜這很好。

+0

謝謝Nitram。我正在考慮以這種方式實施,但是要確保沒有辦法做。提交而不會阻止用戶界面。我確實注意到Commit有一個上下文,但它看起來(不確定)只是關於如何鎖定表或記錄。 –

+0

你的語法也有輕微的改正。而不是'等待Task.Run(AddRecords);'它應該'等待Task.Run(()=> AddRecords());'如果你想更新你的帖子。 –

+0

@SanuelJackson我通常使用VB.net,對不起。但我實際上並不打算在那裏創建一個lambda函數,而是引用一個委託函數。這不就是這樣嗎?關於一般答案:您有什麼擔憂? 'AddRecords'函數將像以前一樣工作,其優點是'await'不會同步到UI,但可以在'ThreadPool'中安排延續。 – Nitram

5

使用ExecuteNonQueryAsync,而不是ExecuteNonQuery

await cmd.ExecuteNonQueryAsync(); 

這是最耗時的查詢,這是因爲您使用的同步版本同步封鎖的呼籲。

編輯:

既然你不需要做這需要一個同步方面的任何UI的工作,你可以使用ConfigureAwait(false),您可以應用到OpenAsync,則延續將在線程池IO運行工人。

+0

UI仍然阻止-.-。我相信這發生在'Commit'上,因爲它似乎是剩下的唯一不是異步的東西。 –

+1

@Sanuel你有'CommitAsync'方法嗎? –

+0

不是我能看到的。沒有注意到這樣的事情,否則會使用它。雖然會再次檢查。 ---確認。沒有'CommitAsync()' –

相關問題