2022年5月アーカイブ

前回は EndInvoke メソッドで非同期デリゲート処理の終了待ちをしましたので、今回は、AsyncWaitHandle プロパティの WaitOne メソッドで終了待ちを試してみる事に。(当然、UIスレッドには不向き)。

★元ネタ-マイクロソフトドキュメントで、ほぼそのままのソース。


<注意点>

・WaitHandle は非同期呼び出しが完了すると通知されるので、WaitOne 呼び出しでこれを待機します。

・待機ハンドルの使用終了と同時にシステムリソースを解放する場合、WaitHandle.Close メソッドを呼び出します。

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

Button2 thread 10 does some work.
Wait Test begins.
The call executed on thread 7, with return value "Wait time was 2000.".

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form1

    Private Delegate Function dlgtAsyncWaitChk(ByVal tm As Integer, ByRef id As Integer) As String

    Private Function waitTest(ByVal term As Integer, ByRef threadId As Integer) As String
        Debug.Print("Wait Test begins.")
        System.Threading.Thread.Sleep(term)
        threadId = System.Threading.Thread.CurrentThread.ManagedThreadId
        Return String.Format("Wait time was {0}.", term.ToString())
    End Function

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim threadId As Integer
        Dim awc As dlgtAsyncWaitChk
        Dim result As IAsyncResult
        Dim retValue As String

        awc = New dlgtAsyncWaitChk(AddressOf waitTest)
        result = awc.BeginInvoke(2000, threadId, Nothing, Nothing)

        Debug.Print("Button2 thread {0} does some work.", System.Threading.Thread.CurrentThread.ManagedThreadId)

        result.AsyncWaitHandle.WaitOne()

        retValue = awc.EndInvoke(threadId, result)

        result.AsyncWaitHandle.Close() ' ガベージコレクションの効率化
        Debug.Print("The call executed on thread {0}, with return value ""{1}"".", threadId, retValue)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

5/16(月)、コアサーバー(coreserver.jp)で最新高速サーバーへの移転メンテナンスが行われた結果、Movable Type 5で、ログインが出来なくなりました。

ログイン不可の症状は、たった一行表示のみ。

『Got an error: Unescaped left brace in regex is illegal here in regex; marked by <- HERE in m/{{ <- HERE support}}/?/』

仕方が無いので検索すると、

★本家Perl?のWebページ「perldiag - さまざまな Perl 診断メッセージ」(https://perldoc.jp/docs/perl/5.28.0/perldiag.pod)に解説が有りました。

Unescaped left brace in regex is illegal here in regex; marked by <-- HERE in m/%s/

(F) 正規表現中で リテラルな "{" 文字 (U+007B LEFT CURLY BRACKET) にマッチングしたいときに 覚えておくべき単純な規則は、何らかの方法で それぞれのリテラルな実体をエスケープすることです。 一般的に一番簡単なのは、"\{" のように逆スラッシュを前置するか、 かっこでかこむ ("[{]") ことです。. パターン区切り文字も中かっこの場合、マッチングする右中かっこ ("}") も、パーサーの混乱を避けるためにエスケープするべきです; 例えば:

 qr{abc\{def\}ghi}

リテラルな "{" 文字にエスケープを強制することにより、 将来のリリースで様々な方法で Perl 言語を拡張できるようになります。 既存のコードを不必要に壊すことを避けるために、 拡張が "{" をリテラルとして使うことと競合しそうにない文脈では 制限は強制されません。


★CORESERVERの運営元 バリューサーバー【サーバーシステムの増強と移行について】のページには、対処法のQ&Aが有りました(https://www.value-server.com/info/brandnew2022.html)。

Q:Movable Typeで「Got an error: Unescaped left brace in regex is illegal here in regex; marked by <-- HERE in m/{{ <-- HERE support}}/?/」が発生しました。

A:こちらのファイルを下記のように修正の上、正常に動作するかご確認下さい。 /virtual/アカウント名/public_html/ドメイン名/lib/MT/App/CMS.pm

4828行目
$css =~ s#{{support}}/?#$app->support_directory_url#ie;

$css =~ s#\{\{support}}/?#$app->support_directory_url#ie;

4830行目
$css =~ s#{{theme_static}}/?#$theme->static_file_url#ie;

$css =~ s#\{\{theme_static}}/?#$theme->static_file_url#ie;


ただ、何行目かは、それぞれお使いのMovable Typeのバージョンによって異なる様です。
<使用中のMTOS5は、4976行目と 4978行目>


★CORESERVER補足情報

・修正ファイルの場所
/public_html/設置したMTのディレクトリ名/lib/MT/App/CMS.pm

非同期デリゲート処理の呼び出し完了待ちを検索していたら、マイクロソフトが言うには、

・呼び出すメソッドと同じシグネチャを持つデリゲートを定義すれば、.NETでは全てのメソッドを非同期的に呼び出すことが出来る。

・デリゲート定義では、共通言語ランタイム(CLR)により適切なシグネチャが使用された BeginInvoke および EndInvoke メソッドが自動的に定義される。

・BeginInvoke メソッドは、非同期的に実行するメソッドと同じパラメーターと、2つの省略可能な追加パラメーターを持っている。
 追加の1つ目は、非同期呼び出しが完了したときに呼び出されるメソッドを参照する AsyncCallback デリゲート
 追加の2つ目は、コールバックメソッドに情報を渡すユーザー定義オブジェクト。

・BeginInvoke は IAsyncResult を返すので、これを使用して非同期呼び出しの進捗状況を監視出来る。

・EndInvoke メソッドは、非同期処理の呼び出し結果を取得し、BeginInvoke の後であればいつでも呼び出すことが出来る。

・EndInvoke のパラメーターには、非同期実行するメソッドの <Out>ByRef と ByRef、BeginInvoke の戻り値 IAsyncResult が含まれる。

らしいので、追加パラメーター2つを省略して(=Nothing)、EndInvokeメソッドで非同期処理の終了待ちを試してみる事に(当然、UIスレッドには不向き)。

★元ネタ-マイクロソフトドキュメントで、ほぼそのままのソース。


<注意点>

・非同期呼び出しが完了していない場合、 EndInvoke は非同期呼び出しが完了するまで呼び出し元スレッドをブロック。

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

Wait Test begins.
Button1 thread 10 does some work.
The call executed on thread 7, with return value "Wait time was 2000.".

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form1

    Private Delegate Function dlgtAsyncWaitChk(ByVal tm As Integer, ByRef id As Integer) As String

    Private Function waitTest(ByVal term As Integer, ByRef threadId As Integer) As String
        Debug.Print("Wait Test begins.")
        System.Threading.Thread.Sleep(term)
        threadId = System.Threading.Thread.CurrentThread.ManagedThreadId
        Return String.Format("Wait time was {0}.", term.ToString())
    End Function

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim threadId As Integer
        Dim awc As dlgtAsyncWaitChk
        Dim result As IAsyncResult
        Dim retValue As String

        awc = New dlgtAsyncWaitChk(AddressOf waitTest)
        result = awc.BeginInvoke(2000, threadId, Nothing, Nothing)

        Debug.Print("Button1 thread {0} does some work.", System.Threading.Thread.CurrentThread.ManagedThreadId)

        retValue = awc.EndInvoke(threadId, result)
        Debug.Print("The call executed on thread {0}, with return value ""{1}"".", threadId, retValue)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/

フリーズしてる訳でもないんだから、プログレスバーでも出しておけば良いんじゃないの?と書いておきながら、非同期処理でUIにプログレスバーを表示してみる事に。

矢張り曲者は、別スレッドからのUIコントロール更新でしょうか。(デリゲートメソッドもコールバックメソッドも、スレッドプールで処理を実行)

【デリゲートメソッドの終了待ち】

・BeginInvoke呼出しメソッドの終了をEndInvokeで受けるのですが、UI(メイン)スレッド中に EndInvoke を呼び出すと非同期(デリゲート)メソッドが完了するまで、呼出し元スレッドがブロックされてしまいます。*1

・UIサービスを提供するスレッドからは、EndInvoke を呼び出さない方が宜しいかと。⇒ コールバックメソッドの利用

*1:IDEのデバッグ実行では、飛んだまま戻ってこず


<注意点>

・仮引数の範囲のチェック(0%~100%のプログレスバー設定値)や何処でチェックするかなどは、多分に好みの問題だと思うので・・・。

・エラー処理は手抜きです。


お試し環境
  Windows7 64bit Edition
  Visual Basic 2008 AnyCPU対象


/*-------------------------------- お試し結果 ------------------------------*/

・ボタン1を押すと、

プログレスバー表示その1

プログレスバー表示その2

・ボタン2を押すと、

プログレスバー表示その3

プログレスバー表示その4

/*----------------------------------------------------------------------------*/


/*----------------------------  お試しソース -------------------------------*/

Public Class Form4

    Private Delegate Sub dlgtWork1Prgbar1(ByVal data As Integer)

    Private Sub Set1Prgbar1Value(ByVal percent As Integer)
        If InvokeRequired = True Then
            Invoke(New dlgtWork1Prgbar1(AddressOf Set1Prgbar1Value), percent)
        Else
            ProgressBar1.Value = percent
            If percent >= 100 Then
                Label1.ForeColor = System.Drawing.Color.Red
                Label1.Text = "完了"
                Button1.Enabled = True
            End If
        End If
    End Sub

    Private Sub work1PrgBar1(ByVal no As Integer)
        For i As Integer = no To 10
            System.Threading.Thread.Sleep(500) ' 時間の掛かる処理の代替
            Set1Prgbar1Value(i * 10)
        Next
    End Sub

    Private Sub bar1Callback1(ByVal ar As IAsyncResult)
        Dim arWk1Prgbar1 As dlgtWork1Prgbar1

        arWk1Prgbar1 = CType(ar.AsyncState, dlgtWork1Prgbar1)
        arWk1Prgbar1.EndInvoke(ar)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim wk1PrgBar1 As dlgtWork1Prgbar1
        Dim ar As IAsyncResult

        Button1.Enabled = False
        Label1.ForeColor = System.Drawing.Color.FromKnownColor(KnownColor.ControlText)
        Label1.Text = "実行中・・・"

        Set1Prgbar1Value(0)
        wk1PrgBar1 = New dlgtWork1Prgbar1(AddressOf work1PrgBar1)
        ar = wk1PrgBar1.BeginInvoke(1, New AsyncCallback(AddressOf bar1Callback1), wk1PrgBar1)
    End Sub

/*--------------------------  お試しソース 改 -----------------------------*/

    Structure prgrsData
        Dim percent As Integer
        Dim leftTm As TimeSpan
    End Structure

    Private Delegate Sub dlgtWork2Prgbar1(ByVal data As prgrsData)

    Private Sub Set2Prgbar1Value(ByVal prgDat As prgrsData)
        If InvokeRequired = True Then
            Invoke(New dlgtWork2Prgbar1(AddressOf Set2Prgbar1Value), prgDat)
        Else
            ProgressBar1.Value = prgDat.percent
            Label1.Text = String.Format("あと {0:0.0} 秒・・・", prgDat.leftTm.TotalSeconds)
            If prgDat.percent >= 100 Then
                Label1.ForeColor = System.Drawing.Color.Red
                Label1.Text = "完了"
                Button2.Enabled = True
            End If
        End If
    End Sub

    Private Sub work2PrgBar1(ByVal prgDat As prgrsData)
        Const Cntr As Integer = 10
        Const MSec As Integer = 500
        Dim pData As prgrsData

        For i As Integer = prgDat.percent To Cntr
            System.Threading.Thread.Sleep(MSec) ' 時間の掛かる処理の代替
            pData.percent = i * Cntr
            pData.leftTm = TimeSpan.FromMilliseconds((Cntr - i) * MSec)
            Set2Prgbar1Value(pData)
        Next
    End Sub

    Private Sub bar1Callback2(ByVal ar As IAsyncResult)
        Dim arWk2Prgbar1 As dlgtWork2Prgbar1

        arWk2Prgbar1 = CType(ar.AsyncState, dlgtWork2Prgbar1)
        arWk2Prgbar1.EndInvoke(ar)
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim wk2Prgbar1 As dlgtWork2Prgbar1
        Dim ar As IAsyncResult
        Dim progDat As prgrsData

        Button2.Enabled = False
        Label1.ForeColor = System.Drawing.Color.FromKnownColor(KnownColor.ControlText)
        Label1.Text = "実行中・・・"

        progDat.percent = 0
        progDat.leftTm = TimeSpan.FromMilliseconds(5000)
        Set2Prgbar1Value(progDat)
        progDat.percent = 1
        wk2Prgbar1 = New dlgtWork2Prgbar1(AddressOf work2PrgBar1)
        ar = wk2Prgbar1.BeginInvoke(progDat, New AsyncCallback(AddressOf bar1Callback2), wk2Prgbar1)
    End Sub

End Class

/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
/*============================================================================*/