- PR -

File.Exists を別スレッド化する。

投稿者投稿内容
jiioi
会議室デビュー日: 2007/09/27
投稿数: 14
投稿日時: 2008-05-22 12:01
お世話になっています。
記事で非同期デリゲートを勉強し、コードを書いてみたのですが、別スレッドの結果をうまく処理できません。
きちんと理解できなくて、申し訳ありませんが教えていただけるとありがたいです。


Class Class1
'非同期で呼び出すメソッドと同じシグネチャのデリゲート
Delegate Function FileExistsAsyncDelegate( _
ByVal FileName As String) As Boolean

'エントリポイント
Public Shared Function FileExistTimeOut( _
ByVal FileName As String, ByVal TimeOut As Integer) As Boolean
'デリゲートオブジェクトの作成
Dim dlgt As New FileExistsAsyncDelegate(AddressOf FileExists)
'非同期呼び出しを開始
Dim ar As IAsyncResult = dlgt.BeginInvoke(FileName, _
New AsyncCallback(AddressOf CallbackMethod), dlgt)
System.Threading.Thread.Sleep(TimeOut)
Return False
End Function

'非同期で呼び出すメソッド
Private Shared Function FileExists(ByVal FileName As String) As Boolean
Return File.Exists(FileName)
End Function

'コールバックメソッド
Private Shared Sub CallbackMethod(ByVal ar As IAsyncResult)
'デリゲートオブジェクトの取得
Dim dlgt As FileExistsAsyncDelegate = _
CType(ar.AsyncState, FileExistsAsyncDelegate)
'EndInvokeを呼び出し、結果を取得
Dim ret As Boolean = dlgt.EndInvoke(ar)
End Sub
End Class
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2008-05-22 12:25
引用:

記事で非同期デリゲートを勉強し、コードを書いてみたのですが、別スレッドの結果をうまく処理できません。



EndInvokeの戻り値を普通にFileExistsの戻り値としてみればよいだけなので
ここまでできていて何が「うまく処理」できないのかわかりません。

このコードは抜粋ですか?
抜粋でないなら、あとはただretを返せばいいだけだと思います。
動作確認はしていませんが。

それと、無理にCallBackを使うことはありません。
忘れてはいけない後処理、例えばリソースの解放などがない限り、
後処理はメインスレッドでも出来ます。

今回はFile.ExistsですからCallbackを使わないほうが
短くて簡単にできると思います。
jiioi
会議室デビュー日: 2007/09/27
投稿数: 14
投稿日時: 2008-05-22 13:27
れいさん、ありがとうございます。
御指導のとおり、CALLBACKをやめて、EndInvokeの戻り値を取得したのですが、アプリケーションがフリーズしてしまいました。

'非同期で呼び出すメソッドと同じシグネチャのデリゲート
Delegate Function FileExistsAsyncDelegate( _
ByVal FileName As String) As Boolean

Public Shared Function FileExistTimeOut( _
ByVal FileName As String, ByVal TimeOut As Integer) As Boolean
'デリゲートオブジェクトの作成
Dim dlgt As New FileExistsAsyncDelegate(AddressOf FileExists)
'非同期呼び出しを開始
Dim ar As IAsyncResult = dlgt.BeginInvoke(FileName, Nothing, Nothing)
'タイムアウト
System.Threading.Thread.Sleep(TimeOut)
'EndInvokeを呼び出し、結果を取得
Dim ret As Boolean = dlgt.EndInvoke(ar)
Return ret
End Function

'非同期で呼び出すメソッド
Private Shared Function FileExists(ByVal FileName As String) As Boolean
Return File.Exists(FileName)
End Function
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2008-05-22 13:37
引用:

御指導のとおり、CALLBACKをやめて、EndInvokeの戻り値を取得したのですが、アプリケーションがフリーズしてしまいました。



EndInvokeは非同期操作が終わるまで待機しますからフリーズしますよ。
それにこれじゃタイムアウトじゃなくてただの待ちです。

ar.AsyncWaitHandle.WaitOne
を使いましょう。
jiioi
会議室デビュー日: 2007/09/27
投稿数: 14
投稿日時: 2008-05-22 14:17
れいさん、タイムアウトのところをar.AsyncWaitHandle.WaitOneにしたらできました!!

WaitOneの引数のBooleanなのですが、「待機する前に同期ドメインを終了するかどうか」というのがよく分かりません。
とりあえずFalseにしました。
すみませんがもう少し教えていただきますようおねがいします。


'タイムアウト
Dim ret As Boolean
If ar.AsyncWaitHandle.WaitOne(TimeOut, False) Then
'EndInvokeを呼び出し、結果を取得
ret = dlgt.EndInvoke(ar)
Else
ret = False
End If
Return ret
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2008-05-22 16:05
引用:

jiioiさんの書き込み (2008-05-22 14:17) より:
れいさん、タイムアウトのところをar.AsyncWaitHandle.WaitOneにしたらできました!!



あー。
いまさらですが、やっとやりたいことが理解できました。
タイムアウト付のExistsメソッドを作りたいのですね?

それならば、EndInvokeを必ず呼ぶように作らないと、
リソースが解放されません。
(気にしないという手もありますが…、私は気にします)

ですので、そのコードではダメです。

いろいろ対処する手はありますが、私がよく使うのは次の2つです。

1.
キャンセルされたIAsyncResultリストを保持しておいて、
定期的に「終わったか」を調べてEndInvokeを呼んで廃棄する

2.
コールバックでEndInvokeを呼ぶ

1の方は同期を考えなくていいので楽で、コードもわかりやすく短いのですが、
モジュール化が困難です。

2の方は局所的なコードになり再利用もしやすいのですが、
同期処理がぐちゃぐちゃです。

という感じで、最初に言ったように私にはいい方法が思いつきません。
IAsyncResultのパターンは非常に応用性が高いのですが、
私には「タイムアウト」や「キャンセル」がうまく作れません。
どのコードも「ぐちゃぐちゃ」です。

いま適当に作った2の方法のコードを挙げておきますが、
上記のようにぐちゃぐちゃです。
誰かいい方法があったら教えてください。
(ちなみにWindows.Formsでは1の方法を普段使っています)

コード:
Class FileUtil
    Delegate Function FileExistsDelegate(ByVal FileName As String) As Boolean

    Shared Sub New()
        _FileExistsDelegate = New FileExistsDelegate(AddressOf File.Exists)
        _FileExistsCallback = New AsyncCallback(AddressOf FileExistsCallback)
    End Sub

    Private Shared _FileExistsDelegate As FileExistsDelegate
    Private Shared _FileExistsCallback As AsyncCallback

    Public Class FileExistsResult
        Public FinishedEvent As New System.Threading.ManualResetEvent(False)
        Public ReturnValue As Boolean
    End Class

    Public Shared Function FileExistWithTimeOut(ByVal FileName As String, ByVal TimeOut As Integer) As Boolean
        Dim result As New FileExistsResult
        Dim ar As IAsyncResult = _FileExistsDelegate.BeginInvoke(FileName, _FileExistsCallback, result)
        If result.FinishedEvent.WaitOne(TimeOut, False) Then
            SyncLock result
                result.FinishedEvent.Close()
            End SyncLock
            Return result.ReturnValue
        End If
        SyncLock result
            result.FinishedEvent.Close()
            result.FinishedEvent = Nothing
        End SyncLock
        Throw New TimeoutException()
    End Function

    Private Shared Sub FileExistsCallback(ByVal ar As IAsyncResult)
        Dim result As FileExistsResult = CType(ar.AsyncState, FileExistsResult)
        result.ReturnValue = _FileExistsDelegate.EndInvoke(ar)
        SyncLock result
            If result.FinishedEvent IsNot Nothing Then result.FinishedEvent.Set()
        End SyncLock
    End Sub

End Class



何回も言いますが、非常に醜いコードです。
なるべく参考にしないことをオススメします。

引用:

WaitOneの引数のBooleanなのですが、「待機する前に同期ドメインを終了するかどうか」というのがよく分かりません。
とりあえずFalseにしました。



SynchronizationAttributeが関与してるときに意味を持ちます。
falseにしておけば再利用しやすくなりますので、とりあえずfalseで。
詳しくはMSDNのWaitHandle周りのドキュメントを読みましょう。
jiioi
会議室デビュー日: 2007/09/27
投稿数: 14
投稿日時: 2008-05-22 16:50
れいさん、そのとおりです。
タイムアウト付きのExistsメソッドを作りたかったのです。
このコードではだめなのですね。
私には、ハードルが高いですが、れいさんのコードをじっくりと噛締めたいと思います。
jiioi
会議室デビュー日: 2007/09/27
投稿数: 14
投稿日時: 2008-05-23 09:17
やっていてふと思ったのですが、タイムアウト付きのExistsメソッドを使っても、タイムアウトするまでアプリケーションがフリーズしてしまいます。
Exists自体を別スレッドに任せた方がよいのでしょうか?
普通はどのように設計するものなのでしょう。
スタンダードな手法等があれば、御教授よろしくおねがいします。

スキルアップ/キャリアアップ(JOB@IT)