web-dev-qa-db-ja.com

ネイティブAPIを呼び出すときのハングの回避

背景

ここには、ほとんどすべてのシステムに統合できるプログラム可能なハードウェアがあります。このハードウェアには、ネイティブのWindows DLL(これにはソースコードがありません))と、上記のライブラリへのラッパーが付属しています。このラッパーは、ネイティブAPIまでは.NETレイヤーまでなので、ネイティブAPIを呼び出すためにこのラッパーを使用します。

このプログラム可能なハードウェアは、非常に問題の多い方法で製品に物理的に統合されています。ハードウェアの一部は、USBケーブルが時々半分に切断されるような方法で製品に取り付けられています...(質問しないでください)問題は、ケーブルが深くなるとケーブルが半分に切断される傾向があることですネイティブAPIに。これが発生すると、呼び出し元はネイティブAPI関数から戻りません。以下は、wrapperを介した呼び出しのスニペットです。

    public static UInt32 Wrap(UInt32 configval, Int32 channum, IntPtr handleval)
    {
        if (IntPtr.Size == 4)
            return Native32(configval, channum, handleval);
        else
            return Native64(configval, channum, handleval);
    }

質問

APIの内部でハングしないようにする一般的な方法はありますか?

タスクを開始してタイムアウトで待つことができることはすでに知っています(タイムアウトした場合は再開してください)。この方法は機能しますが、これを行う他の方法はありますか?私は、API内で何が行われているのかを制御できません。

5
Snoop

基本的に問題は、決して戻らない呼び出しをブロックすることです。これは、すべてのネットワーク呼び出しが失敗する可能性が非常に高いため、タイムアウトが組み込まれたソケットなどの問題が解決された問題です。ある意味では、問題は、コードがこれが可能な場合に応答に失敗しないことを想定していることです。これを回避する唯一の方法は、その呼び出しのブロックを停止することです。なんらかの非同期機能が必要になります。ある種の非ブロッキングを使用するかどうかIOまたはスレッドを生成するかどうかにかかわらず、ソリューションには同じ種類の高レベルの設計があります。

ソリューションは、全体的なアーキテクチャに大きく依存します。ワークキューを中心に構成されている場合、最も良い解決策は、APIが戻った(またはタイムアウトした)ときに、ワークキューに応答をプッシュして処理することです。一方、アプリケーションのシーケンシャル性が高い場合は、応答が返される(またはタイムアウトになる)までメインスレッドをブロックする必要があります。

どちらのアプローチでも、APIの所要時間は1つの問題です。応答時間が非常に規則的である(分散が小さい)場合は、タイムアウトを失敗として受け入れるだけです。応答時間に適切な上限を設定できない場合は、タイムアウトにもう1つの手順を実行して、APIに別の要求(できれば高速)を送信して、まだ接続されているかどうかを確認することができます。それでも接続できる場合は、コードが切断されないため、少し待ってから必要に応じて繰り返すことができます。

7
JimmyJames

決して戻らない関数呼び出しから制御を戻す唯一の方法は、側面からそれを監視し、迅速に応答しない場合は制御することです。戻り値のない戻り呼び出しを取得する他の方法はありません。

したがって、タスクは機能しますが、問題を切り分けたい場合は、ラッパー内でスレッドを開始して呼び出しを行うことができるため、メインのラッパーメソッドがスレッドの応答のタイムアウトを処理できます。これにより、.NETクライアントコードが非常に簡単になります。呼び出しを行うと、ラッパー内に非同期呼び出しを配置する代わりに、50ミリ秒後に「データなし」の応答が返される場合があります(これはあまり問題ではありません)。公平に、スレッドとそのハンドルでWaitForSingleObjectを開始し、タイムアウト)

1
gbjbaanb

.Netを使用しているので、タイムアウトを構成して、呼び出しをタスクに入れませんか? MSDNには、Task.Waitを使用した、これに関する素晴らしい例があります。

https://msdn.Microsoft.com/en-us/library/dd235606(v = vs.110).aspx

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Task t = Task.Run( () => {
                            Random rnd = new Random();
                            long sum = 0;
                            int n = 5000000;
                            for (int ctr = 1; ctr <= n; ctr++) {
                               int number = rnd.Next(0, 101);
                               sum += number;
                            }
                            Console.WriteLine("Total:   {0:N0}", sum);
                            Console.WriteLine("Mean:    {0:N2}", sum/n);
                            Console.WriteLine("N:       {0:N0}", n);   
                         } );
     TimeSpan ts = TimeSpan.FromMilliseconds(150);
     if (! t.Wait(ts))
        Console.WriteLine("The timeout interval elapsed.");
   }
}
// The example displays output similar to the following:
//       Total:   50,015,714
//       Mean:    50.02
//       N:       1,000,000
// Or it displays the following output:
//      The timeout interval elapsed.
1
Machado

この種の問題に対するいくつかの解決策を見てきました。それらはすべて、通常はパイプを介して接続される、またはMicrosoft AddIn Framework MAFを使用する個別のプロセスに関係しています。最後のものはどうやら複雑で、メインプロセスと分離されたプロセスの間に大量の中間クラスがあり、依存プロセスに一意の識別名を付けることはできません。

次のような潜在的に深刻なバグを持つネイティブコンポーネントを処理する唯一のオプションのようです

  • 呼び出しから戻らない(スレッドレベルでも処理できる)
  • メモリリーク
  • プロセスの終了につながる重大な実行時エラー

または、64ビット.NETプロセスで32ビットDLLなど)のアーキテクチャのギャップを埋める必要がある場合。

1
Erik Hart