Evitare blocchi quando si chiama un'API nativa

5

Sfondo

Abbiamo qui un pezzo di hardware programmabile al lavoro che integriamo in quasi tutti i nostri sistemi. Questo pezzo di hardware è venuto con una DLL nativa di Windows (per la quale non abbiamo il codice sorgente) e un wrapper per detta libreria. Questo wrapper è responsabile solo del marshalling delle chiamate verso l'API nativa fino al livello .NET e nulla più. Pertanto, utilizziamo questo wrapper per effettuare chiamate all'API nativa.

Questo pezzo di hardware programmabile è fisicamente integrato nel nostro prodotto in un modo molto problematico. Il pezzo di hardware si trova nel prodotto in modo tale che ogni tanto il cavo USB viene tagliato a metà ... (non chiedere) Il problema è che il cavo tende a dimezzarsi quando siamo in profondità nell'API nativa. Quando ciò accade, il chiamante non ritorna dalla funzione API nativa. Di seguito è riportato un frammento di come appare la chiamata tramite 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);
    }

Domanda

Esiste un metodo generale per assicurarci che non ci si blocchi all'interno dell'API?

So già che posso iniziare un'attività e attendere con un timeout (riprendi solo se scade). Questo metodo funziona, ma esiste un altro modo per farlo? Non ho alcun controllo su ciò che accade all'interno dell'API.

    
posta Snoop 10.03.2016 - 18:36
fonte

4 risposte

7

Fondamentalmente il problema sta bloccando una chiamata che non tornerà mai più. Questo è un problema risolto in quanto ogni chiamata di rete ha molte probabilità di fallire, quindi abbiamo cose come socket con timeout integrati a loro. In un certo senso, il problema è che il tuo codice presuppone che questo non mancherà di rispondere quando potrà. L'unico modo per aggirare è smettere di bloccare su quella chiamata. Avrai bisogno di una sorta di capacità asincrona. Sia che si utilizzi una sorta di IO non bloccante o si generino una discussione, la soluzione avrà lo stesso tipo di progettazione di alto livello.

La soluzione dipende molto dall'architettura generale. Se sei strutturato attorno a una coda di lavoro, la soluzione migliore è quando l'API restituisce (o va in timeout) la risposta alla coda di lavoro e la gestione. D'altra parte, se la tua applicazione è altamente sequenziale, probabilmente devi bloccare il thread principale fino a quando la risposta non ritorna (o scade).

In entrambi gli approcci, un problema è quanto tempo ci si può aspettare che l'API prenda. Se il tempo di risposta è molto regolare (bassa varianza), puoi semplicemente accettare il timeout come errore. Se non è possibile impostare un limite superiore ragionevole per il tempo di risposta, è possibile che si desideri eseguire un ulteriore passaggio in timeout per inviare un'altra richiesta all'API (preferibilmente qualcosa di veloce) per verificare se si è ancora connessi. Se riesci ancora a connetterti, il cavo non viene tagliato e puoi aspettare ancora e ripetere se necessario.

    
risposta data 21.03.2016 - 20:26
fonte
1

L'unico modo per restituire il controllo da una chiamata di funzione che non ritorna mai, è guardarlo da un lato e schiaffarlo se non risponde rapidamente. Non c'è altro modo per ottenere una chiamata di non ritorno per tornare.

Quindi un'attività potrebbe funzionare, ma se si desidera isolare il problema, è possibile avviare un thread all'interno del wrapper per effettuare la chiamata, in modo che il metodo del wrapper principale possa gestire un timeout sulla risposta del thread. Questo renderebbe molto semplice il tuo codice client .NET - effettui una chiamata e potrebbe tornare dopo 50 ms con una risposta "senza dati", a scapito di mettere la chiamata asincrona all'interno del wrapper (che non è troppo disturbo per essere equo, avviare una discussione e WaitForSingleObject sul suo handle e un timeout)

    
risposta data 22.03.2016 - 18:17
fonte
1

Ho visto diverse soluzioni a questo tipo di problemi, tutti relativi a processi separati, che di solito sono collegati tramite pipe o che utilizzano il Microsoft AddIn Framework MAF. L'ultimo è in qualche modo complesso, con una tonnellata di classi intermedie tra il processo principale e quello isolato, e non può avere un nome identificativo univoco per il processo dipendente.

Sembra l'unica opzione per gestire i componenti nativi che hanno bug potenzialmente gravi, come

  • non ritorno da una chiamata (può anche essere gestito a livello di thread)
  • perdite di memoria
  • errori di runtime gravi che portano alla chiusura del processo

o dove è necessario colmare le lacune dell'architettura, ad esempio DLL a 32 bit con processo .NET a 64 bit.

    
risposta data 25.03.2016 - 11:50
fonte
1

Dato che stai usando .Net, perché non metti la tua chiamata in una Task, con un timeout configurato? MSDN ne ha un bell'esempio, usando Task.Wait:

link

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.
    
risposta data 22.03.2016 - 20:22
fonte

Leggi altre domande sui tag