非同期DB更新方法について への返答
投稿で使用できる特殊コードの説明。(別タブで開きます。)
以下の返答は逆順(新しい順)に並んでいます。
投稿者 YuO  (社会人)
投稿日時
2016/9/28 13:05:46
> ContinueWithを使用した実装処理はどのように実装すればよいのでしょうか。
まじめに書くとAsync/Awaitを使うのに比べて非常に面倒になります。
なので,Async/Awaitだけで書いた方がよいと思います。
まず,単純にメソッドを一度まとめてしまうと,
DELETEに失敗した時点でBulk Copyする意味が無いため,Try-Catchの範囲を変更していますが。
なお,Async/Awaitを使っている場合,UIスレッドから呼び出されたとすると,
・Awaitしているメソッドが内部でTaskを作っているような場合は非UIスレッド
・それ以外の部分はUIスレッドで動作します。
# ConfigureAwait(False)をしている場合を除く。この場合はメソッド終了まで非UIスレッドで動きます。
メソッドに分割し直すと,
UIに関係ない部分をメソッドに切り出して,ConfigureAwait(False)によってUIスレッドに戻す処理を行わないようにしています。
ちなみに,これらをContinueWith使って書き直すのは結構骨が折れます。
なお,Async/Awaitは状態機械を作るので,上記とは異なるコードを作成します。
まじめに書くとAsync/Awaitを使うのに比べて非常に面倒になります。
なので,Async/Awaitだけで書いた方がよいと思います。
まず,単純にメソッドを一度まとめてしまうと,
Private Async Function UpdateTableAsync(ByVal tableName As String, ByVal dt As DataTable) As Task
Try
'対象のテーブルを一括削除。
Using con As New SqlConnection(strConnection)
Using cmd As New SqlCommand()
Await con.OpenAsync()
cmd.Connection = con
cmd.CommandText = "DELETE FROM " & tableName
cmd.CommandTimeout = 3600
Await cmd.ExecuteNonQueryAsync()
con.Close()
End Using
End Using
'対象のテーブルにBulkCopy。
Using con As New SqlConnection(strConnection)
Using sqlBulkCopy As New SqlBulkCopy(con)
sqlBulkCopy.DestinationTableName = tableName
sqlBulkCopy.BulkCopyTimeout = 3600
Await con.OpenAsync()
Await sqlBulkCopy.WriteToServerAsync(dt)
con.Close()
End Using
End Using
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Function
のようになります。DELETEに失敗した時点でBulk Copyする意味が無いため,Try-Catchの範囲を変更していますが。
なお,Async/Awaitを使っている場合,UIスレッドから呼び出されたとすると,
・Awaitしているメソッドが内部でTaskを作っているような場合は非UIスレッド
・それ以外の部分はUIスレッドで動作します。
# ConfigureAwait(False)をしている場合を除く。この場合はメソッド終了まで非UIスレッドで動きます。
メソッドに分割し直すと,
'対象のテーブルを一括削除。
Private Async Function DeleteTableAsync(ByVal tableName As String) As Task
Using con As New SqlConnection(strConnection)
Using cmd As New SqlCommand()
Await con.OpenAsync().ConfigureAwait(False)
cmd.Connection = con
cmd.CommandText = "DELETE FROM " & tableName
cmd.CommandTimeout = 3600
Await cmd.ExecuteNonQueryAsync().ConfigureAwait(False)
con.Close()
End Using
End Using
End Function
'対象のテーブルにBulkCopy。
Private Async Function BulkCopyToTableAsync(ByVal tableName As String, ByVal dt As DataTable) As Task
Using con As New SqlConnection(strConnection)
Using sqlBulkCopy As New SqlBulkCopy(con)
sqlBulkCopy.DestinationTableName = tableName
sqlBulkCopy.BulkCopyTimeout = 3600
Await con.OpenAsync().ConfigureAwait(False)
Await sqlBulkCopy.WriteToServerAsync(dt).ConfigureAwait(False)
con.Close()
End Using
End Using
End Function
Private Async Function UpdateTableAsync(ByVal tableName As String, ByVal dt As DataTable) As Task
Try
Await DeleteTableAsync(tableName)
Await BulkCopyToTableAsync(tableName, dt)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Function
でしょうか。UIに関係ない部分をメソッドに切り出して,ConfigureAwait(False)によってUIスレッドに戻す処理を行わないようにしています。
ちなみに,これらをContinueWith使って書き直すのは結構骨が折れます。
Private Function UpdateTableAsync(ByVal tableName As String, ByVal dt As DataTable) As Task
Dim task1 = DeleteTableAsync(tableName)
Dim task2 = task1.ContinueWith(Function (t) BulkCopyToTableAsync(tableName, dt) End Function, TaskContinuationOptions.OnlyOnRanToCompletion).UnWrap()
Dim task3 = task1.ContinueWith(Sub (t) MessageBox.Show(t.Exception.Message) End Sub, CancellationToken.None,TaskContinuationOptions.OnlyOnOnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext())
Dim task4 = task2.ContinueWith(Sub (t) MessageBox.Show(t.Exception.Message) End Sub, CancellationToken.None,TaskContinuationOptions.OnlyOnOnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext())
Return Task.WhenAll(task3, task4)
End Function
たぶん,こんな感じです (コンパイルすらしていません)。なお,Async/Awaitは状態機械を作るので,上記とは異なるコードを作成します。
投稿者 YUU  (社会人)
投稿日時
2016/9/28 11:22:28
YuO様、返信いただきありがとうございます。
>DELETE→BULK COPYでないといけない作業ですよね。
その認識で誤りございません。順序を適切に守る必要のあるメソッドではあるのですがどうしてもUIの硬直が気になり非同期デビューした次第です。
AsyncAwaitが導入された4.5以降書きやすくなっているとはいわれていますが、いまひとつサンプルが少なく四苦八苦しております。
現状のコードではTask1とTask2が平行処理されてしまうのですね。テーブルの整合性をチェックしても問題なく更新できているようなので気付きませんでした。(たまたま?。)
ContinueWithを使用した実装処理はどのように実装すればよいのでしょうか。
>ただ,MessageBox.ShowするならばUIスレッドで呼んだ方が良いでしょう。
invokeするということでしょうか?メッセージを表示するのもUIスレッドに当たるのですね。
お手数おかけいたします。
>DELETE→BULK COPYでないといけない作業ですよね。
その認識で誤りございません。順序を適切に守る必要のあるメソッドではあるのですがどうしてもUIの硬直が気になり非同期デビューした次第です。
AsyncAwaitが導入された4.5以降書きやすくなっているとはいわれていますが、いまひとつサンプルが少なく四苦八苦しております。
現状のコードではTask1とTask2が平行処理されてしまうのですね。テーブルの整合性をチェックしても問題なく更新できているようなので気付きませんでした。(たまたま?。)
ContinueWithを使用した実装処理はどのように実装すればよいのでしょうか。
>ただ,MessageBox.ShowするならばUIスレッドで呼んだ方が良いでしょう。
invokeするということでしょうか?メッセージを表示するのもUIスレッドに当たるのですね。
お手数おかけいたします。
投稿者 YuO  (社会人)
投稿日時
2016/9/27 12:59:15
同一テーブルへの操作で,本来順序のある作業ですよね。
つまり,DELETE→BULK COPYでもBULK COPY→DELETEでもよいわけではなく,
DELETE→BULK COPYでないといけない作業ですよね。
そうであるならば,この2つを並列なタスクとしてはいけません。
DELETE後にBULK COPYを行う一つのタスクにするか,ContinueWithによる継続タスクにするかです。
エラーの表示に関して,MessageBox.ShowがUIスレッド以外から呼ばれて良いかどうかはわかりません。
WinForms/WPFの常識で言えば,UI要素はUIスレッド以外から触れないのでダメですが,
MSDNにはUIスレッド以外から呼んだときに例外が発生するようなことが書かれていないので。
ただ,MessageBox.ShowするならばUIスレッドで呼んだ方が良いでしょう。
つまり,DELETE→BULK COPYでもBULK COPY→DELETEでもよいわけではなく,
DELETE→BULK COPYでないといけない作業ですよね。
そうであるならば,この2つを並列なタスクとしてはいけません。
DELETE後にBULK COPYを行う一つのタスクにするか,ContinueWithによる継続タスクにするかです。
エラーの表示に関して,MessageBox.ShowがUIスレッド以外から呼ばれて良いかどうかはわかりません。
WinForms/WPFの常識で言えば,UI要素はUIスレッド以外から触れないのでダメですが,
MSDNにはUIスレッド以外から呼んだときに例外が発生するようなことが書かれていないので。
ただ,MessageBox.ShowするならばUIスレッドで呼んだ方が良いでしょう。
投稿者 YUU  (社会人)
投稿日時
2016/9/26 18:15:19
現在下記のコードにてテーブル更新処理を行っております。
一応、上記のコードにて当方の目的どおり更新されているのですが疑問がございます。
○Task1とTask2の実装方法は適切でしょうか。いろいろ調べているとロック等が必要?。
Task1とTask2は同時に実行されてしまうというような記事を見つけました。削除されていない状態でBulkCopyは使用できないのですが現在は実行できているように見受けられます。WhenAllでの処理待ちはこの場合適切?。
○エラー処理の実装は適切なのでしょうか。デバック時にエラーが表示されず、スルーされているようなのですが。
お知恵お貸ししていただけますと幸いです。
''' <summary>
''' テーブルデータの削除
''' </summary>
''' <param name="tableName"></param>
''' <returns></returns>
Private Async Function DeleteTable(ByVal tableName As String) As Task
Try
Using con As New SqlConnection(strConnection)
Using cmd As New SqlCommand()
Await con.OpenAsync()
cmd.Connection = con
cmd.CommandText = "DELETE FROM " & tableName
cmd.CommandTimeout = 3600
Await cmd.ExecuteNonQueryAsync()
con.Close()
End Using
End Using
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Function
''' <summary>
''' BulkCopyを利用したテーブル更新。
''' </summary>
''' <param name="tableName">テーブル名</param>
''' <param name="dt">データテーブル</param>
''' <returns></returns>
Private Async Function UpdateTableAsync(ByVal tableName As String, ByVal dt As DataTable) As Task
'対象のテーブルを一括削除。
Dim Task1 = DeleteTable(tableName)
'対象のテーブルにBulkCopy。
Dim Task2 = Task.Run(Sub()
Try
Using con As New SqlConnection(strConnection)
Using sqlBulkCopy As New SqlBulkCopy(con)
sqlBulkCopy.DestinationTableName = tableName
sqlBulkCopy.BulkCopyTimeout = 3600
con.Open()
sqlBulkCopy.WriteToServer(dt)
con.Close()
End Using
End Using
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub)
Await Task.WhenAll(Task1, Task2)
End Function
一応、上記のコードにて当方の目的どおり更新されているのですが疑問がございます。
○Task1とTask2の実装方法は適切でしょうか。いろいろ調べているとロック等が必要?。
Task1とTask2は同時に実行されてしまうというような記事を見つけました。削除されていない状態でBulkCopyは使用できないのですが現在は実行できているように見受けられます。WhenAllでの処理待ちはこの場合適切?。
○エラー処理の実装は適切なのでしょうか。デバック時にエラーが表示されず、スルーされているようなのですが。
お知恵お貸ししていただけますと幸いです。
メソッドを分割する手法をとることにしました。
UIスレッドと非UIスレッドを正しく認識し利用する必要がありそうですね。
>ちなみに,これらをContinueWith使って書き直すのは結構骨が折れます。
>たぶん,こんな感じです (コンパイルすらしていません)。
コンパイルが出来ませんでした。task2~4で怒られます。
task2に関しては、下記で通りました。
task3と4はこれらで通りませんでした。
また、これら更新メソッドを呼ぶ側にも気になる点がございます。
○buttonイベントで呼び出しているのですが、ボタンの状態offとgifの表示がワンクッション遅れてしまいます。Asyncの特性なのでしょうか?。それとも画面の更新が追いついていないだけでしょうか。