C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

ツリー一括表示

スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 11:53) #92298
Re[1]: スレッドをタスクで書くには /WebSurfer (19/09/10(Tue) 12:51) #92299
│└ Re[2]: スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 14:00) #92301
│  ├ Re[3]: スレッドをタスクで書くには /魔界の仮面弁士 (19/09/10(Tue) 14:12) #92302
│  └ Re[3]: スレッドをタスクで書くには /WebSurfer (19/09/10(Tue) 14:30) #92304
│    └ Re[4]: スレッドをタスクで書くには /WebSurfer (19/09/10(Tue) 14:52) #92305
Re[1]: スレッドをタスクで書くには /魔界の仮面弁士 (19/09/10(Tue) 13:35) #92300
  └ Re[2]: スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 14:28) #92303
    ├ Re[3]: スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 15:24) #92308
    │└ Re[4]: スレッドをタスクで書くには /WebSurfer (19/09/10(Tue) 15:50) #92313
    └ Re[3]: スレッドをタスクで書くには /魔界の仮面弁士 (19/09/10(Tue) 16:09) #92316
      └ Re[4]: スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 16:41) #92322
        └ Re[5]: スレッドをタスクで書くには /魔界の仮面弁士 (19/09/10(Tue) 17:01) #92324
          └ Re[6]: スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 18:55) #92328
            └ Re[7]: スレッドをタスクで書くには /魔界の仮面弁士 (19/09/10(Tue) 20:36) #92329
              └ Re[8]: スレッドをタスクで書くには /TanuTanu (19/09/10(Tue) 23:37) #92330 解決済み
                ├ Re[9]: スレッドをタスクで書くには /WebSurfer (19/09/11(Wed) 10:43) #92337
                └ Re[9]: スレッドをタスクで書くには /WebSurfer (19/09/11(Wed) 13:00) #92342


親記事 / ▼[ 92299 ] ▼[ 92300 ]
■92298 / 親階層)  スレッドをタスクで書くには
□投稿者/ TanuTanu (48回)-(2019/09/10(Tue) 11:53:12)

分類:[VB.NET/VB2005 以降] 

いつもお世話になっております。

下記スレッドでは動作しました。

Dim t As New Thread(New ThreadStart(AddressOf Form1.worker))
t.Start()

タスクに書き換えたいのですが方法が解りません。

Dim _Task As Task = Task.Run(
Sub()

Form1.worker()

End Sub
)


ご教授のほど宜しくお願いいたします。

FORM1側***********************************

Public Delegate Sub DATA_READ_Delegate()

Public Sub DATA_READ()

Debug.WriteLine(Me.DataSet1.TESTデータ.Rows(0)("TEST").ToString())

End Sub

Public Sub worker()

Me.Invoke(New DATA_READ_Delegate(AddressOf DATA_READ))

End Sub
[ □ Tree ] 返信 編集キー/

▲[ 92298 ] / ▼[ 92301 ]
■92299 / 1階層)  Re[1]: スレッドをタスクで書くには
□投稿者/ WebSurfer (1901回)-(2019/09/10(Tue) 12:51:41)
No92298 (TanuTanu さん) に返信

何をしたいのかよく分かりませんが・・・

ひょっとして、デリゲートを利用した非同期メソッドを .NET 4.5 以降で使えるようになった
async / await / Task を利用して書き換えたいということですか?

であれば、以下の記事が参考になりませんか?

デリゲートを利用した非同期メソッドの実装
http://surferonwww.info/BlogEngine/post/2019/06/19/coding-asynchronous-method-by-using-delegate-in-windows-forms-application.aspx
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92299 ] / ▼[ 92302 ] ▼[ 92304 ]
■92301 / 2階層)  Re[2]: スレッドをタスクで書くには
□投稿者/ TanuTanu (49回)-(2019/09/10(Tue) 14:00:29)
WebSurfer さん

ありがとうございます。
すいません小生、初心者である事を明記しておりませんでした。

>ひょっとして、デリゲートを利用した非同期メソッドを .NET 4.5 以降で使えるようになった
>async / await / Task を利用して書き換えたいということですか?

はい。その通りです。

紹介頂いた記事で解った事はasync / await / Taskを使用するばデリゲートは不要って事でした。

冒頭で書いたコードですが、Task内でForm1のDataTableを取得すると下記エラーが出ました。

 例外がスローされました: 'System.IndexOutOfRangeException' (System.Data.dll の中)

次にDelegateを書いてみたら、下記エラーが出ました。

 例外がスローされました: 'System.InvalidOperationException' (System.Windows.Forms.dll の中)

どうしたらいいものか困り果てております。
出来ましたらご教授の程宜しくお願いいたします。



[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92301 ] / 返信無し
■92302 / 3階層)  Re[3]: スレッドをタスクで書くには
□投稿者/ 魔界の仮面弁士 (2366回)-(2019/09/10(Tue) 14:12:04)
No92301 (TanuTanu さん) に返信
>>> Debug.WriteLine(Me.DataSet1.TESTデータ.Rows(0)("TEST").ToString())
> 例外がスローされました: 'System.IndexOutOfRangeException' (System.Data.dll の中)

IndexOutOfRangeException とのことなので
  Me.DataSet1.TESTデータ.Rows.Count が 0 の状態で、.Rows(0) にアクセスしている
という状況であると予想します。

もしも TEST 列が無い場合は、ArgumentException になりそう。
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92301 ] / ▼[ 92305 ]
■92304 / 3階層)  Re[3]: スレッドをタスクで書くには
□投稿者/ WebSurfer (1902回)-(2019/09/10(Tue) 14:30:48)
No92301 (TanuTanu さん) に返信

とりあえず取得したいデータは、

Me.DataSet1.TESTデータ.Rows(0)("TEST").ToString()

でよくて、非同期メソッドを使わないで(デリゲートも async/await/Task も一切使わないで)、
普通に Form の中でそのコードを書いたら期待通り取得できるのでしょうか?

そうであれば、async/await/Task を使った非同期でそのデータを取得するなら、私が紹介した
記事のコードを例にとると、以下のようにしてみてはいかがですか?

// テスト用の時間がかかるメソッド
private string TimeCosumingMethod(DataSet dataset)
{
    return dataset.TESTデータ.Rows(0)("TEST").ToString();
}

// async/await/Task を使った非同期呼び出し
private async void button3_Click(object sender, EventArgs e)
{
    this.label1.Text = await Task.Run(() => TimeCosumingMethod(this.DataSet1));
}

[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92304 ] / 返信無し
■92305 / 4階層)  Re[4]: スレッドをタスクで書くには
□投稿者/ WebSurfer (1903回)-(2019/09/10(Tue) 14:52:45)
No92304 の「テスト用の時間がかかるメソッド」のコードで VB.NET と C# がゴッチャになって
ました。すみません。

×: return dataset.TESTデータ.Rows(0)("TEST").ToString();

〇: return dataset.TESTデータ.Rows[0]["TEST"].ToString();
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92298 ] / ▼[ 92303 ]
■92300 / 1階層)  Re[1]: スレッドをタスクで書くには
□投稿者/ 魔界の仮面弁士 (2365回)-(2019/09/10(Tue) 13:35:19)
2019/09/10(Tue) 13:36:45 編集(投稿者)

No92298 (TanuTanu さん) に返信
> Public Sub DATA_READ()
>  Debug.WriteLine(Me.DataSet1.TESTデータ.Rows(0)("TEST").ToString())
> End Sub

この DataSet は、UI スレッドからの読み書きが行われますか?

Task を使うにせよ Thread を使うにせよ、
DataSet への編集操作はスレッドセーフではないので、複数のスレッドから
同時に読み書きが行われるのであれば、排他制御処理が必要となります。
※読取専用で使うのであれば、スレッドセーフが保証されている。

基本的には、「インスタンスを生成するスレッド」と「それを利用するスレッド」が
同一であることが望ましいです(フィールド変数ではなくローカル変数とする)。


Task を利用するに当たり、ワーカースレッドを使うか UI スレッドを使うかを指定したい場合は、
ContinueWith メソッドの引数に TaskScheduler インスタンスを渡すことができます。

過去ログで言うとこのあたり。 VB ではなくて C# ですけれども…。
http://bbs.wankuma.com/index.cgi?mode=al2&namber=68409&KLOG=116
http://bbs.wankuma.com/index.cgi?mode=al2&namber=91647&KLOG=158
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92300 ] / ▼[ 92308 ] ▼[ 92316 ]
■92303 / 2階層)  Re[2]: スレッドをタスクで書くには
□投稿者/ TanuTanu (50回)-(2019/09/10(Tue) 14:28:17)
魔界の仮面弁士 さん

ありがとうございます。

実施したい内容は、下記となります。
CSV書き込みに数秒ほど遅れる時があるのでワーカースレッドで処理させようという目論見です。

UI スレッド:何かしらの処理 → MessageBox表示
              ↓                      
              ワーカースレッド:Task → Table読出し → CSVへの書き込み→UI スレッドに戻る

この処理をしている時は複数のスレッドから同時に読み書きが行われる事はありません。

宜しくお願いいたします。
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92303 ] / ▼[ 92313 ]
■92308 / 3階層)  Re[3]: スレッドをタスクで書くには
□投稿者/ TanuTanu (51回)-(2019/09/10(Tue) 15:24:29)
WebSurfer さん

返信ありがとうございます。

DataSet参照渡しした後TESTデータメンバにありませんのエラーになる為、知識不足の為
仕方なく文字列送ってみましたが System.IndexOutOfRangeException になってしまいます。
ソースは下記となります。


Public Function TimeCosumingMethod(ByRef STR As String)

Debug.WriteLine(STR)

Return True

End Function

Dim _Task As Task = Task.Run(
Sub()

Form1.TimeCosumingMethod(Form1.DataSet1.TESTデータ.Rows(0)("TEST").ToString())

End Sub
)

[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92308 ] / 返信無し
■92313 / 4階層)  Re[4]: スレッドをタスクで書くには
□投稿者/ WebSurfer (1905回)-(2019/09/10(Tue) 15:50:55)
No92308 (TanuTanu さん) に返信

自分は C# でしかコードは書けませんが、それで話が伝わるでしょうか?

ダメなら VB.NET 使いの人の回答をお待ちください。
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92303 ] / ▼[ 92322 ]
■92316 / 3階層)  Re[3]: スレッドをタスクで書くには
□投稿者/ 魔界の仮面弁士 (2369回)-(2019/09/10(Tue) 16:09:35)
No92303 (TanuTanu さん) に返信
> 実施したい内容は、下記となります。
No92302 で指摘した .Rows.Count の状態はどうでしたか?


> CSV書き込みに数秒ほど遅れる時があるのでワーカースレッドで処理させようという目論見です。
であればフィールド変数を共用するのではなく、
ワーカースレッドを呼び出す際に、パラメーターとして
「DataSetのコピー」を渡すようにした方が安全かと思います。

もしもその DataSet を DataGridView 等にバインドしている場合には、
必ずしもスレッドセーフではなくなります。たとえば列ヘッダーのクリックや
セル編集などといった操作によって、DataSet の状態が変更される可能性があるためです。


> この処理をしている時は複数のスレッドから同時に読み書きが行われる事はありません。
複数のスレッドから同時に読み込むのであれば OK ですが、
いずれかのスレッドが書き込み中は、それを他のスレッドから読み込む事も書き込むことも NG ですね。

DataSet をフィールド変数として共有するのであれば、
「DataSet が編集中」であることを保証する仕組みを設けた上で、
編集中は読み取りできないことを保証するコードを設けた方が安全です。
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92316 ] / ▼[ 92324 ]
■92322 / 4階層)  Re[4]: スレッドをタスクで書くには
□投稿者/ TanuTanu (52回)-(2019/09/10(Tue) 16:41:13)
魔界の仮面弁士 さん

返信ありがとうございます。

カウント結果:0でした。
スレッドでは取得できるんですがね・・・なぜなんでしょうか。

Public Sub worker()

Debug.WriteLine(Me.DataSet1.TESTデータ.Rows.Count)

End Sub


>CSV書き込みに数秒ほど遅れる時があるのでワーカースレッドで処理させようという目論見です。
> であればフィールド変数を共用するのではなく、
> ワーカースレッドを呼び出す際に、パラメーターとして
> 「DataSetのコピー」を渡すようにした方が安全かと思います。

DataSetのコピーですね。承知しました。

宜しくお願いいたします。



[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92322 ] / ▼[ 92328 ]
■92324 / 5階層)  Re[5]: スレッドをタスクで書くには
□投稿者/ 魔界の仮面弁士 (2372回)-(2019/09/10(Tue) 17:01:27)
No92322 (TanuTanu さん) に返信
> スレッドでは取得できるんですがね・・・なぜなんでしょうか。
上記のいう「スレッド」が何を意味しているのか曖昧ですが:

UI スレッドにとっての Me.DataSet1 と
ワーカースレッドの Me.DataSet1 が
別のインスタンスになっていませんか?

とりあえず下記では、すべて「1」と表示されます。


Public Class Form1
 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  Dim newRow = Me.DataSet1.TESTデータ.NewTESTデータRow()
  '
  'newRow の各列に値をセットする処理をここに記述
  '
  Me.DataSet1.TESTデータ.AddTESTデータRow(newRow)
  Me.DataSet1.AcceptChanges()
  MsgBox(Me.DataSet1.TESTデータ.Count, MsgBoxStyle.SystemModal, "Load")
 End Sub

 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  Task.Run(Sub() MsgBox(Me.DataSet1.TESTデータ.Count, MsgBoxStyle.SystemModal, "Button1"))
 End Sub
 Private Async Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
  MsgBox(Await Task.Run(Function() Me.DataSet1.TESTデータ.Count), MsgBoxStyle.SystemModal, "Button2")
 End Sub
End Class
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92324 ] / ▼[ 92329 ]
■92328 / 6階層)  Re[6]: スレッドをタスクで書くには
□投稿者/ TanuTanu (53回)-(2019/09/10(Tue) 18:55:17)
魔界の仮面弁士 さん

ありがとうございました。

別クラスでShared使っていたので、これを止めてForm1渡せば上手くいきました。

一つ伺ってもいいでしょうか。

ただまだ解っていないところがありまして、Timer1_Tickを使っているのですが

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

DIM _他クラス AS NEW 他クラス(Form1)

_他クラス=NOTHING

End Sub

のような使い方は、_他クラス=NOTHINGをしないと
_他クラスのインスタンスが増殖してしまうと思っていいのでしょうか?


[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92328 ] / ▼[ 92330 ]
■92329 / 7階層)  Re[7]: スレッドをタスクで書くには
□投稿者/ 魔界の仮面弁士 (2373回)-(2019/09/10(Tue) 20:36:13)
2019/09/10(Tue) 21:05:58 編集(投稿者)

No92328 (TanuTanu さん) に返信
> 別クラスでShared使っていたので、これを止めてForm1渡せば上手くいきました。

Shared やフィールド変数は、スレッド間で無闇に共有しないほうが良いのですけれどね。

で… Form1 はスレッドセーフなオブジェクトではありません。
DataSet は、読み取りに関してのみスレッドセーフが保証されていますが、
Form のメンバーは、たとえ読み取りのみであってもスレッドセーフではありませんので、
Form1 を非 UI スレッドに渡さないようにしてください。



> ただまだ解っていないところがありまして、Timer1_Tickを使っているのですが
> Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
> DIM _他クラス AS NEW 他クラス(Form1)

念のためにお聞きしますが、「他クラス」の中でワーカースレッドを起動して、
その中で、引数の Form1 インスタンスを利用していたりはしないですよね?


それはさておき、「New 他クラス(Form1)」と呼び出した場合の Form1 は、
データ型としての Form1 クラスのことではなく、
VB 固有の My.Forms.Form1 プロパティ相当の意味になりますよね。

いわゆる「既定のフォーム インスタンス」などと呼ばれる機能ですが、
この方式での Form へのアクセスは避けることを強くお奨めします。
マルチスレッドでの開発を手がけようというのであれば。


> のような使い方は、_他クラス=NOTHINGをしないと
> _他クラスのインスタンスが増殖してしまうと思っていいのでしょうか?

Nothing 代入は不要です。
使用している変数 `_他クラス` はローカル変数なので、
何もせずとも、End Sub 到達時点で自動的に処分されます。

解放前に適切な終了処理が必要なもの(たとえば IDisposable など)の場合は、
Finally 等を併用するべきですが、そうでないなら作りっぱなしで構いません。

もし仮に、すべてのクラスが Nothing 代入を必要とするのだとしたら、
As String な変数一つ一つにも Nothing しなければならないという事になってしまいます。


ただ、そもそも「他クラス」のインスタンスを Tick イベントのたびに New しなおす必要が
あるかどうかも検討してみてください。処理内容によっては、「他クラス」のインスタンスを
フィールド変数などとして保持しておき、イベント側ではそれを使いまわすだけで十分かもしれません。
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92329 ] / ▼[ 92337 ] ▼[ 92342 ]
■92330 / 8階層)  Re[8]: スレッドをタスクで書くには
□投稿者/ TanuTanu (54回)-(2019/09/10(Tue) 23:37:05)
魔界の仮面弁士 様、WebSurfer 様

スレッドセーフを意識して無闇にマルチスレッド多用しないように
注意していこうと思います。

まだまだ未熟者ではありますがこの掲示板と指導して頂ける先生方が

居られる事でとても励みになり心の支えとなりこれからも継続して続

けていけそうです。

この度は本当にありがとうございました。





解決済み
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92330 ] / 返信無し
■92337 / 9階層)  Re[9]: スレッドをタスクで書くには
□投稿者/ WebSurfer (1908回)-(2019/09/11(Wed) 10:43:12)
No92330 (TanuTanu さん) に返信

このスレッドの課題は、最初の私のレスで確認させていただいた、

> デリゲートを利用した非同期メソッドを .NET 4.5 以降で使えるようになった
> async / await / Task を利用して書き換えたい

だったはずですが、それは解決したのでしょうか?
[ 親 92298 / □ Tree ] 返信 編集キー/

▲[ 92330 ] / 返信無し
■92342 / 9階層)  Re[9]: スレッドをタスクで書くには
□投稿者/ WebSurfer (1910回)-(2019/09/11(Wed) 13:00:11)
No92330 (TanuTanu さん) に返信

VB.NET のコードとしてはイマイチかもしれませんが、async/await/Task を使った
非同期メソッドのコードを貼っておきます。

NorthWindProducts は Visual Studio のデータソース構成ウィザードで作った型付
DataSet ですが、要するに質問者さんのコードの 

DataSet1.TESTデータ.Rows(0)("TEST").ToString()

と同様に、

DataSet1.Products.Rows(0)("ProductName").ToString()

で文字列を取得できると思ってください。

Public Class Form3
    Dim DataSet1 As NorthWindProducts

    Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Me.Label1.Text += Await Task.Run(
            Function() As String
                Return Me.TimeCosumingMethod(DataSet1)
            End Function
        )
    End Sub

    Private Function TimeCosumingMethod(ds As NorthWindProducts) As String
        System.Threading.Thread.Sleep(3000)
        Return ds.Products.Rows(0)("ProductName").ToString() &
            " + ManagedThreadId: " & System.Threading.Thread.CurrentThread.ManagedThreadId
    End Function

    Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.DataSet1 = New NorthWindProducts
        Dim Adapter As New NorthWindProductsTableAdapters.ProductsTableAdapter
        Adapter.Fill(Me.DataSet1.Products)

        Me.Label1.Text = "ManagedThreadId: " &
            System.Threading.Thread.CurrentThread.ManagedThreadId & " / "
    End Sub
End Class

[ 親 92298 / □ Tree ] 返信 編集キー/


管理者用

- Child Tree -