Domanda

Non sono mai stato completamente soddisfatto del modo in cui funziona la gestione delle eccezioni, ci sono molte eccezioni e try/catch porta sul tavolo (svolgimento dello stack, ecc.), ma sembra rompere gran parte del modello OO nel processo.

Ad ogni modo, ecco il problema:

Supponiamo che tu abbia una classe che racchiude o include operazioni IO di file in rete (ad es.leggere e scrivere su qualche file in un particolare percorso UNC da qualche parte).Per vari motivi non vuoi che le operazioni di I/O falliscano, quindi se rilevi che falliscono, riprovale e continui a riprovarle finché non hanno esito positivo o raggiungi un timeout.Ho già una comoda classe RetryTimer che posso istanziare e utilizzare per sospendere il thread corrente tra i tentativi e determinare quando è trascorso il periodo di timeout, ecc.

Il problema è che hai un sacco di operazioni IO in diversi metodi di questa classe e devi racchiuderle ciascuna nella logica try-catch/retry.

Ecco uno snippet di codice di esempio:

RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
bool success = false;
while (!success)
{
    try
    {
        // do some file IO which may succeed or fail
        success = true;
    }
    catch (IOException e)
    {
        if (fileIORetryTimer.HasExceededRetryTimeout)
        {
            throw e;
        }
        fileIORetryTimer.SleepUntilNextRetry();
    }
}

Quindi, come evitare di duplicare la maggior parte di questo codice per ogni operazione di I/O di file in tutta la classe?La mia soluzione era utilizzare blocchi delegati anonimi e un singolo metodo nella classe che eseguiva il blocco delegato che gli veniva passato.Questo mi ha permesso di fare cose come queste in altri metodi:

this.RetryFileIO( delegate()
    {
        // some code block
    } );

Questo mi piace un po', ma lascia molto a desiderare.Mi piacerebbe sapere come altre persone potrebbero risolvere questo tipo di problema.

È stato utile?

Soluzione

Sembra un'eccellente opportunità per dare un'occhiata alla programmazione orientata agli aspetti.Ecco un buon articolo su AOP in .NET.L'idea generale è che si estrarrebbero le preoccupazioni interfunzionali (ad es.Riprova per x ore) in una classe separata e quindi annoteresti tutti i metodi che devono modificare il loro comportamento in quel modo.Ecco come potrebbe apparire (con un bel metodo di estensione su Int32)

[RetryFor( 10.Hours() )]
public void DeleteArchive()
{
  //.. code to just delete the archive
}

Altri suggerimenti

Mi chiedevo solo: cosa ritieni che il tuo metodo lasci a desiderare?Potresti sostituire il delegato anonimo con un...di nome?delegato, qualcosa del genere

    public delegate void IoOperation(params string[] parameters);

    public void FileDeleteOperation(params string[] fileName)
    {
        File.Delete(fileName[0]);
    }

    public void FileCopyOperation(params string[] fileNames)
    {
        File.Copy(fileNames[0], fileNames[1]);
    }

    public void RetryFileIO(IoOperation operation, params string[] parameters)
    {
        RetryTimer fileIORetryTimer = new RetryTimer(TimeSpan.FromHours(10));
        bool success = false;
        while (!success)
        {
            try
            {
                operation(parameters);
                success = true;
            }
            catch (IOException e)
            {
                if (fileIORetryTimer.HasExceededRetryTimeout)
                {
                    throw;
                }
                fileIORetryTimer.SleepUntilNextRetry();
            }
        }
    }

    public void Foo()
    {
        this.RetryFileIO(FileDeleteOperation, "L:\file.to.delete" );
        this.RetryFileIO(FileCopyOperation, "L:\file.to.copy.source", "L:\file.to.copy.destination" );
    }

Potresti anche utilizzare un approccio più OO:

  • Crea una classe base che gestisca gli errori e chiami un metodo astratto per eseguire il lavoro concreto.(Modello metodo modello)
  • Crea classi concrete per ogni operazione.

Ciò ha il vantaggio di dare un nome a ciascun tipo di operazione eseguita e di fornire uno schema di comando: le operazioni sono state rappresentate come oggetti.

Ecco cosa ho fatto di recente.Probabilmente è stato fatto meglio altrove, ma sembra abbastanza pulito e riutilizzabile.

Ho un metodo di utilità che assomiglia a questo:

    public delegate void WorkMethod();

    static public void DoAndRetry(WorkMethod wm, int maxRetries)
    {
        int curRetries = 0;
        do
        {
            try
            {
                wm.Invoke();
                return;
            }
            catch (Exception e)
            {
                curRetries++;
                if (curRetries > maxRetries)
                {
                    throw new Exception("Maximum retries reached", e);
                }
            }
        } while (true);
    }

Quindi nella mia applicazione utilizzo la sintassi dell'espressione Lamda di C# per mantenere le cose in ordine:

Utility.DoAndRetry( () => ie.GoTo(url), 5);

Questo chiama il mio metodo e riprova fino a 5 volte.Al quinto tentativo, l'eccezione originale viene lanciata nuovamente all'interno di un'eccezione di nuovo tentativo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top