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

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

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

関数の同時実行(?)について

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■92752 / inTopicNo.1)  関数の同時実行(?)について
  
□投稿者/ Ito (1回)-(2019/10/28(Mon) 17:14:52)

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

2019/10/28(Mon) 17:26:33 編集(投稿者)
VisualStudio2019 Community(VisualBasic)でWindowsFormアプリの作成をしています。

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
        Timer1.Interval = 1000
        Timer2.Interval = 1000
        Timer1.Start()
        Timer2.Start()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Timer1.Stop()
        Test(1)
        Timer1.Start()
    End Sub

    Private Sub Timer2_Tick(sender As Object, e As EventArgs) Handles Timer2.Tick
        Timer2.Stop()
        Test(2)
        Timer2.Start()
    End Sub

    Private Sub Test(pVal as Integer)
        Dim wk as integer = pVal
           :
           :
        wk変数を用いた何らかの処理
           :
           :
    End Sub

上記処理において Test関数はタイマーから呼ばれたとき別々の処理となるのでしょうか?
それともほぼ同時に並列して実行されるのでしょうか?
 ⇒例えばTest関数内のwk変数が処理中に変わってしまうようなことがあるのか?

処理のすぐ終わるような関数では余り気にしなくても良いのかもしれませんが
Test関数自体が少し時間のかかる処理だった場合どうなってしまうのか気になったため質問させて頂きました。

※実際は複数の外部機器とRS232Cにて通信を行っており、受信したデータのチェックや処理はほぼ同じなため
 関数化しているが、ほぼ同時に受信した場合問題が出ないか心配になったため上記のような質問となっております。

よろしくお願いします。

引用返信 編集キー/
■92753 / inTopicNo.2)  Re[1]: 関数の同時実行(?)について
□投稿者/ Hongliang (909回)-(2019/10/28(Mon) 17:31:12)
タイマにはいくつか種類があってそれによっても変わるのですが、とりあえずお使いのタイマはSystem.Windows.Forms.Timerのようなのでそれを前提に。

このタイマの場合、Tickイベントは全て画面と同じスレッドで実行されます。
なので、Timer1のTickイベント(の中で呼び出しているTestメソッド)が処理中の場合、Timer2のTickイベントはTimer1のTickイベントが終了するまで待たされることになります。
// この挙動が、Itoさんが「別々の動作」と表現するものと一致しているかどうかは分かりませんが…。

なお、画面と同じスレッドで動くという仕様上、Tickイベント内で秒単位の時間がかかるような処理を行うべきではありません。
引用返信 編集キー/
■92754 / inTopicNo.3)  Re[1]: 関数の同時実行(?)について
□投稿者/ PANG2 (323回)-(2019/10/28(Mon) 18:05:24)
No92752 (Ito さん) に返信
> ※実際は複数の外部機器とRS232Cにて通信を行っており、受信したデータのチェックや処理はほぼ同じなため
>  関数化しているが、ほぼ同時に受信した場合問題が出ないか心配になったため上記のような質問となっております。

SerialPort.DataReceivedイベントの話?

SerialPort.DataReceived Event
https://docs.microsoft.com/ja-jp/dotnet/api/system.io.ports.serialport.datareceived
> イベントDataReceivedは、 SerialPortオブジェクトからデータを受信するときに、セカンダリスレッドで発生します。

とあるので、

System.Windows.Forms.Timerではなく
System.Threading.Timerが実験に適しています。

引用返信 編集キー/
■92755 / inTopicNo.4)  Re[1]: 関数の同時実行(?)について
□投稿者/ shu (1201回)-(2019/10/28(Mon) 18:10:31)
No92752 (Ito さん) に返信

> 上記処理において Test関数はタイマーから呼ばれたとき別々の処理となるのでしょうか?
> それともほぼ同時に並列して実行されるのでしょうか?
>  ⇒例えばTest関数内のwk変数が処理中に変わってしまうようなことがあるのか?
>
その他の部分の自動実行については既に言及されているので
Test関数内の話だけですと、同時実行されても個々にDimの変数が用意されるので衝突することは
ありません。
引用返信 編集キー/
■92756 / inTopicNo.5)  Re[2]: 関数の同時実行(?)について
□投稿者/ Ito (2回)-(2019/10/28(Mon) 18:13:41)
No92753 (Hongliang さん) に返信
> このタイマの場合、Tickイベントは全て画面と同じスレッドで実行されます。
> なので、Timer1のTickイベント(の中で呼び出しているTestメソッド)が処理中の場合、Timer2のTickイベントはTimer1のTickイベントが終了するまで待たされることになります。
> // この挙動が、Itoさんが「別々の動作」と表現するものと一致しているかどうかは分かりませんが…。
⇒なるほど、納得しました。
 分かりやすい説明有り難うございます。

> なお、画面と同じスレッドで動くという仕様上、Tickイベント内で秒単位の時間がかかるような処理を行うべきではありません。
⇒はい、それは心得てはいたのですが、サンプルとしたソースで検証出来るのではと思ったのと簡単にかけたため
 タイマーイベントを使用しました。

で、実際の所RS232Cの通信で受信した時を処理するロジックにて疑問に思ったため
実際の処理に近いソースの提示が必要であることが分かったので以下に示します。(若干省略しております。)

    Delegate Sub ExecCommandDelegater(ByVal pStr As List(Of Byte))
    Private cmdDataAna1 As New List(Of Byte)
    Private cmdDataAna2 As New List(Of Byte)

    Private Sub Serial1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles Serial1.DataReceived

        Dim wRDCnt As Integer = Serial1.BytesToRead
        If wRDCnt < 1 Then
            Exit Sub
        End If

        Dim wDT(wRDCnt) As Byte
        Serial1.Read(wDT, 0, wRDCnt)

        For i As Integer = 0 To wRDCnt - 1
            If wDT(i) = &H2 Then            'コマンドの先頭
                cmdDataAna1.Clear()
            End If
            cmdDataAna1.Add(wDT(i))
            If wDT(i) = &H3 Then            'コマンドの終端
                Invoke(New ExecCommandDelegater(AddressOf ExecCommand), cmdDataAna1)
            End If
        Next i

    End Sub

    Private Sub Serial2_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles Serial2.DataReceived

        Dim wRDCnt As Integer = Serial2.BytesToRead
        If wRDCnt < 1 Then
            Exit Sub
        End If

        Dim wDT(wRDCnt) As Byte
        Serial2.Read(wDT, 0, wRDCnt)

        For i As Integer = 0 To wRDCnt - 1
            If wDT(i) = &H2 Then            'コマンドの先頭
                cmdDataAna2.Clear()
            End If
            cmdDataAna2.Add(wDT(i))
            If wDT(i) = &H3 Then            'コマンドの終端
                Invoke(New ExecCommandDelegater(AddressOf ExecCommand), cmdDataAna2)
            End If
        Next i

    End Sub

    Private Sub ExecCommand(ByVal pStr As List(Of Byte))
        :
        :
        :
      受信データの処理
        :
        :
        :

    End Sub

※現在はSerial2_DataReceived内のInvokeで定義している「ExecCommand」が「ExecCommand2」となっており
 「ExecCommand」と「ExecCommand2」の内容はほぼ一緒です。
 前任者がなぜこのようにしたのかは不明ですが(同時実行を回避するため??)これを上記の
 ようにした場合問題があるかを知りたいです。

よろしくお願いします。


引用返信 編集キー/
■92757 / inTopicNo.6)  Re[2]: 関数の同時実行(?)について
□投稿者/ Ito (3回)-(2019/10/28(Mon) 18:18:15)
No92754 (PANG2 さん) に返信
> ■No92752 (Ito さん) に返信
>>※実際は複数の外部機器とRS232Cにて通信を行っており、受信したデータのチェックや処理はほぼ同じなため
>> 関数化しているが、ほぼ同時に受信した場合問題が出ないか心配になったため上記のような質問となっております。
>
> SerialPort.DataReceivedイベントの話?
⇒はい、実際はDataReceived以降の処理に付いての話です。
 (後出しみたいになってしまい申し訳ありません。)
 実際に使用しているソースに近い形を再度書き込みましたので見て頂けると幸いです。

> SerialPort.DataReceived Event
> https://docs.microsoft.com/ja-jp/dotnet/api/system.io.ports.serialport.datareceived
>>イベントDataReceivedは、 SerialPortオブジェクトからデータを受信するときに、セカンダリスレッドで発生します。
>
> とあるので、
>
> System.Windows.Forms.Timerではなく
> System.Threading.Timerが実験に適しています。
⇒なるほど、とても勉強になります。
 後ほどになりますが、勉強しておこうと思います。
 有り難うございます。
引用返信 編集キー/
■92758 / inTopicNo.7)  Re[2]: 関数の同時実行(?)について
□投稿者/ Ito (4回)-(2019/10/28(Mon) 18:21:53)
No92755 (shu さん) に返信
> Test関数内の話だけですと、同時実行されても個々にDimの変数が用意されるので衝突することは
> ありません。
⇒有り難うございます。
 同時実行の場合同じ変数となってしまうのではと思っていたのですっきりしました。
 
引用返信 編集キー/
■92762 / inTopicNo.8)  Re[3]: 関数の同時実行(?)について
□投稿者/ PANG2 (325回)-(2019/10/28(Mon) 22:45:53)
> ※現在はSerial2_DataReceived内のInvokeで定義している「ExecCommand」が「ExecCommand2」となっており
>  「ExecCommand」と「ExecCommand2」の内容はほぼ一緒です。
>  前任者がなぜこのようにしたのかは不明ですが(同時実行を回避するため??)これを上記の
>  ようにした場合問題があるかを知りたいです。

ExecCommandとExecCommand2はメインスレッドで排他的に実行されます。
Serial1とSerial2の処理を並列的に行いたいなら、駄目ですね。
ExecCommandとExecCommand2で、コントロールを触る部分だけをメインスレッドと同期(Invoke)するべきですね。



引用返信 編集キー/
■92764 / inTopicNo.9)  Re[4]: 関数の同時実行(?)について
□投稿者/ Ito (5回)-(2019/10/29(Tue) 09:32:08)
No92762 (PANG2 さん) に返信
> ExecCommandとExecCommand2はメインスレッドで排他的に実行されます。
> Serial1とSerial2の処理を並列的に行いたいなら、駄目ですね。
> ExecCommandとExecCommand2で、コントロールを触る部分だけをメインスレッドと同期(Invoke)するべきですね。
⇒ExecCommandが呼ばれた後ExecCommand2が呼ばれるとExecCommandが終了するまでExecCommand2が
 待たされるという認識で間違いないでしょうか?
 また、ExecCommandを共通化した場合あとから呼ばれた(Invoke)ExecCommandは先に呼ばれた処理が完了するまで
 待たされると言うことでしょうか?
 ※現在外部機器が6つ(同じ機器)あり通信に関する処理はほぼ同じであるため共通化出来ない物かと
  考えていたところです。(同じような処理を6つ書かないといけないのと1つ直すのに6箇所の修正となってしまうため)
引用返信 編集キー/
■92767 / inTopicNo.10)  Re[5]: 関数の同時実行(?)について
□投稿者/ kiku (138回)-(2019/10/29(Tue) 11:39:35)
質問者さんの質問趣旨と大分ずれてしまっているかもしれませんが、
とりあえず、こうした方がいいなと思うことを記述します。
もし実践しようとした場合、大改修になることと
必要になる知識も多くなることから慎重に進めて頂ければと思います。


Serial1_DataReceivedはできるだけ早く終了させる必要があります。
その理由は、Serial1_DataReceived処理中でも、
Serial1は受信し続けているため、単位時間あたりの受信量よりも処理量が少ない場合、
Serial1の受信バッファを上回る可能性があるためです。

ご提示頂いたコードですと、下記のような流れになり、
コマンドの1つを完全に処理終了しないと次のコマンドを受信できないようになっています。
 (a)Serial1_DataReceived開始(シリアル1のイベント発生)
 (b)InvokeでUIスレッドの空きを待つ
 (c)UIスレッドでExecCommand実行開始
 (d)UIスレッドでExecCommand実行終了
 (e)Invokeの処理終了
 (f)Serial1_DataReceived終了

また、上記UIスレッドでは、同時にExecCommandを実行できないため、
※例外はありますが。。
例えば、Serial1_DataReceived経由の
UIスレッド上でのExecCommandが実行中に、
Serial2_DataReceived経由の
UIスレッド上でのExecCommandを実行しようとした場合、
上記(b)で待たされることになります。

全体的な構造を見直す必要があり、
受信と処理と表示を分離する必要があります。

例えば、下記のような構造が望ましいです。
このような構造にすれば、
単位時間あたりの受信量よりも処理量が少ない場合でも
キューで吸収してくれます。

●シリアル1受信イベント
 ※コマンドを保存したら、すぐに受信イベント終了
 ・コマンド先頭、終端判定
 ・キューにコマンド追加(排他制御必要)
 ・シリアル1受信イベント終了

●シリアル2受信イベント
 ※コマンドを保存したら、すぐに受信イベント終了
 ・コマンド開始、終了判定
 ・キューにコマンド追加(排他制御必要)
 ・シリアル2受信イベント終了

●コマンド処理用スレッド
 ※アプリ起動時に作成し、アプリ終了まで動作するもの
 ※UIスレッドでないもの
 ・キューからコマンドを取り出す(排他制御必要)
 ・コマンドの処理を行う。
 ・コマンドに対するなんらかの表示が必要なら、
  invokeを利用してUIスレッドで表示。
  ※できるだけinvokeの内容は処理時間は短くする。
 ・キューからコマンド取り出しを繰り返す。

引用返信 編集キー/
■92770 / inTopicNo.11)  Re[6]: 関数の同時実行(?)について
□投稿者/ Ito (6回)-(2019/10/29(Tue) 12:03:14)
No92767 (kiku さん) に返信
> Serial1_DataReceivedはできるだけ早く終了させる必要があります。
> その理由は、Serial1_DataReceived処理中でも、
> Serial1は受信し続けているため、単位時間あたりの受信量よりも処理量が少ない場合、
> Serial1の受信バッファを上回る可能性があるためです。
⇒この件については把握していたのですが、今回は1コマンド受信後こちらから
 データ送信するまで次のコマンドが送信されない仕様のため受信量の問題は無いと
 思っておりました。
 

> ご提示頂いたコードですと、下記のような流れになり、
> コマンドの1つを完全に処理終了しないと次のコマンドを受信できないようになっています。
>  (a)Serial1_DataReceived開始(シリアル1のイベント発生)
>  (b)InvokeでUIスレッドの空きを待つ
>  (c)UIスレッドでExecCommand実行開始
>  (d)UIスレッドでExecCommand実行終了
>  (e)Invokeの処理終了
>  (f)Serial1_DataReceived終了
>
> また、上記UIスレッドでは、同時にExecCommandを実行できないため、
> ※例外はありますが。。
> 例えば、Serial1_DataReceived経由の
> UIスレッド上でのExecCommandが実行中に、
> Serial2_DataReceived経由の
> UIスレッド上でのExecCommandを実行しようとした場合、
> 上記(b)で待たされることになります。
⇒まさにこの流れが知りたかった内容です。
 非常に分かりやすく説明して頂き有り難うございます。
 
> 全体的な構造を見直す必要があり、
> 受信と処理と表示を分離する必要があります。
>
> 例えば、下記のような構造が望ましいです。
> このような構造にすれば、
> 単位時間あたりの受信量よりも処理量が少ない場合でも
> キューで吸収してくれます。
⇒非常に有用な情報有り難うございます。
 とても参考になります。
 今回はキューへの変更は時間的な制約の為難しいですが
 今後も似たような開発があるかと思いますので参考に
 させて頂こうと思います。

引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ