4

パイプを使用して、レガシーアプリケーション(COBOLで記述)を.NETの新しいアプリケーションに接続しています。考え方は単純です。レガシープログラム(私のERPメニュー)はストリームにいくつかのパラメーターを書き込み、.NETアプリはストリームを介してそれを読み取り、Console.In要求された画面を開く新しいスレッドを開始します。アイデアがどのように機能するかについての.NET側の抜粋を次に示します。

<STAThread(), LoaderOptimization(LoaderOptimization.MultiDomain)>
Shared Sub Main()
    If Environment.GetCommandLineArgs(1) = "PIPE"
       While True
          Dim pipeParam as String
          Try
             pipeParam = Console.In.ReadLine()
          Catch e as Exception
             Exit Sub
          End Try

          ' Deal with parameters here

          If pipeParam = "END"
             Dim newThread as New Threading.Thread(Sub()
                                                       ' Create a new AppDomain and Loads the menu option, generally a Winforms form.
                                                   End Sub)
             newThread.Start()                 
          End If
       End While

    End If
End Sub

すべてがうまく機能し、簡単でした...今日まで。このソリューションをクライアント環境(Windows Server 2003)に展開しましたが、呼び出されたプロセス(COBOL)が終了している(つまり、Console.In強制的に閉じられている)場合を除いて、要求されたスレッドが実行されていませんでした。それ以降、要求されたすべてのWinFormが表示され、期待どおりに動作し始めます。

この奇妙な振る舞いをログで掘り下げると、IDictionary.ContainsKey()ステートメントが実行されるポイント(またはネイティブコードの実行を必要とする他のメソッド)までスレッドが正常に実行されていることがわかりました。この時点で、スレッドはフリーズ/スリープ状態でした。

スレッドの作成をたとえば3つに制限すると、作成されたすべてのスレッドが3番目のスレッド、つまりConsole.In.ReadLine実行されなくなるまでハングすることがあります。

何を試してみればいいですか?何かアドバイスはありますか?

さらに詳しい情報:これまでに見つけた最も近い方向は、この質問に対するHans Passantの回答でした: マルチスレッドc#アプリケーションでインターフェイスがフリーズします(.NET SystemEventsがdebbugerスレッドリストに表示されますが、解決できませんでした提案された解決策に関する私の問題)。

更新されたニュース

サブスレッドがのロードを完了するのを待つことで、この問題を解決できますForm。この「IsReady」信号はとを通過しAppDomain.SetData()ますAppDomain.GetData()。どういうわけか、フォームの作成後、メインスレッドが続行してもサブスレッドはフリーズしなくなりましたConsole.ReadLine。問題は解決しましたが、私はこれに興味を持っています。私はこれを「可能な限り単純な」テストケースで再現しようとしています。

いくつかの詳細

  • エントリポイントの.exeは32ビットにコンパイルされます。他のすべてのライブラリは「AnyCpu」です。この問題は、32ビット(クライアント)と64ビット(開発)の両方のマシン(Windows Server 2003の両方)で実行されている場合に発生します。
  • Sub Main()上記のスニペットの属性を更新しました。
  • Console.ReadLineをワーカースレッドに入れようとしました。解決しませんでした(下の画像を参照)。
  • COBOLアプリケーションは、別のOSプロセスで実行されるため、フリーズしません。パイプはたまたま私のIPCアプローチです。この場合のCOBOLアプリケーションは、パラメーターを書き込むだけで、応答を待ちません。
  • スタックトレースは下の画像にあります(PRX001235データベースに接続する前、およびフォームを効果的にロードする前に、スレッドはxml構成ファイルを逆シリアル化しています-この場合、まだマネージコードにあるようです-PRX001235スレッドがネイティブコードでフリーズすることがありますデータベースに接続しようとしています):

スレッドとStackTraceの

4

3 に答える 3

4

まず、あなたは非常に珍しいことをしているので、異常な結果は予想外ではありません。第二に、あなたがしていることは、 WindowsSDKドキュメントで厳密に冗長化されています。これは、シングルスレッドのアパートメントを作成したスレッドがブロッキング呼び出しを行うことを許可されないことを示しています。

明らかに、ワーカースレッドは、オペレーティングシステムの腸内のどこかにあるロックでブロックされています。これは、プログラムのメインスレッドによって取得されるロックです。ブロックされたスレッドの1つの呼び出しスタックを確認して、ロックの可能性を特定すると非常に役立ちます。これには、アンマネージコードのデバッグを有効にして、アンマネージスタックフレームを表示できるようにし、Microsoft Symbol Serverを有効にして、Windowsコードのデバッグシンボルを取得し、スタックトレースを理解できるようにする必要があります。

見るものがないので、推測する必要があります。

  • Console.ReadLine()自体は、コンソールを複数のスレッドから安全に使用できるようにする内部ロックを取得し、Read()への呼び出しをシリアル化します。Consoleメソッドを使用するスレッドは、同じロックにヒットする可能性があります。これらのワーカースレッドもコンソールを使用している可能性は低いため、これが原因である可能性は低いです。

  • 厳密に冗長角度は、COM機能であるアパートのねじ切りの関連性に関連付けられています。STAは、プログラムのMain()メソッドの[STAThread]属性、またはThread.SetApartmentState()呼び出しによって有効になります。STAは、Windows、クリップボード、ドラッグアンドドロップ、OpenFileDialogなどのシェルダイアログや他の多くのCOMコクラスなど、基本的にスレッドセーフではないコンポーネントを使用する必要があります。これらのコンポーネントの一部は、.NETクラスでラップされているため認識できない場合があります。 。STAアパートメントは、これらのコンポーネントがスレッドセーフな方法で使用されることを保証します。ワーカースレッドからのそのようなコンポーネントのメソッドへの呼び出しは、それを作成したスレッドに自動的にマーシャリングされます。Control.Invoke()とまったく同じです。このようなマーシャリングでは、スレッドがメッセージループをポンピングして、適切なスレッドに呼び出しをディスパッチする必要があります。そしてあなたのメインスレッドはConsole.ReadLine()呼び出しでブロックされている場合はポンピングしません。メインスレッドが再びポンピングを開始するまで、ワーカースレッドは呼び出しで停止します。ReadLine()呼び出しが最終的に完了するため、実際には完全にデッドロックするわけではありませんが、デッドロックの可能性は非常に高くなります。注目すべきは、CLRがこの種のデッドロックを回避し、lockキーワードを使用するか、同期オブジェクトでWaitOne()を呼び出すか、.NETプログラミングで一般的な種類のブロッキング呼び出しであるThread.Join()を呼び出すと、メッセージループをポンピングすることです。ただし、少なくともMarkが示すように.NET 4.0の回避策が実行されるまでは、Console.ReadLine()に対してこれを実行しません。

  • フォームが作成されるのを待つことで問題を回避するという観察によって引き起こされた、非常に推測的なもの。64ビットオペレーティングシステムでこの問題が発生する可能性があり、EXEプロジェクトのプラットフォームターゲット設定が「AnyCPU」ではなく「x86」に設定されています。その場合、プログラムは、64ビットオペレーティングシステムが32ビットプログラムを実行できるようにするWOW64エミュレーションレイヤーで実行されます。Windowsウィンドウマネージャーは64ビットサブシステムであり、32ビットウィンドウに通知を送信するために呼び出す呼び出しは、ビットネスを切り替えるサンクを通過します。ウィンドウマネージャがWM_SHOWWINDOWメッセージを配信したときにトリガーされるForm.Loadイベントに問題があります。64ビットから32ビットの境界を数回移動するため、エミュレーションレイヤーは困難な状態になります。この答え。Loadイベントが発生したときに、このコードがエミュレーターのロックを保持する可能性は非常に高くなります。したがって、LoadイベントでConsole.ReadLine()を呼び出すのは非常に面倒な場合があります。このロックは、API呼び出しを行うときに32ビットワーカースレッドによっても渡されると思います。非常に推測的ですが、プラットフォームターゲットをAnyCPUに変更できるかどうかを簡単にテストできます。

解決策が非常に単純であることを考えると、この問題の原因を追跡する価値があるかどうかはわかりません。メインスレッドでConsole.ReadLine()を呼び出さないでください。代わりに、ワーカースレッドで呼び出してください。これにより、COBOLプログラムが応答しなくなったときにUIがフリーズするのを防ぐこともできます。ただし、受け取ったものがUIも更新する場合は、Control.Begin / Invoke()を使用してマーシャリングする必要があることに注意してください。

于 2012-11-22T15:49:49.643 に答える
2

.NET 3.5 /2.0および.NET4.0とReflectorの比較:.NET 4.0は常に明示的に呼び出し__ConsoleStream.WaitForAvailableConsoleInput(hFile)__ConsoleStream.ReadFileNative行い、.NET 3.5/2.0で同等の呼び出しを見つけることができません。

パイプをチェックし、WaitForAvailableConsoleInput InternalCallそうであれば、データが利用可能かどうか、またはパイプが閉じているかどうかを待つことを避けます。


私が理解していることを要約すると、この問題の現在の状態です。メインスレッドが呼び出しを続行できるようにする前に、他のスレッド(AppDomains)がメッセージをポンピングしていることを確認することで回避できますReadLine

ReadLineサーバーのバージョンの間にWindowsメッセージが発生していることを意味し、ポンピングが必要であることを知るには十分であるため、これは(ほぼ)最終的な答えになると思います。これは、Windowsメッセージングが必要/使用される場所について言及する必要があるWindowsドキュメントに帰着します。

于 2012-11-21T02:19:47.987 に答える
0

Thread.IsBackground = trueを設定して、これらのスレッドをバックグラウンドスレッドに設定してみましたか?

于 2012-11-21T10:34:02.070 に答える