Elaborazione sequenziale dei comandi con una nuvola asincrona io verso l'alto

1

Siamo nuovi in c # e stiamo ancora cercando di ingannare gli idiomi asincroni.

Abbiamo un servizio Windows che ci richiede di iterare un elenco di risultati interrogati da un database PC per alimentare il server cloud parse (che supporta solo letture e scritture asincrone). I risultati devono essere sovrascritti con nuovi risultati.

In un sdk java legacy, abbiamo semplicemente fatto:

For each result:

do synchronous find
if not found
 create a new object/document
else
 Update the existing object/document
Save synchronously

e non ha avuto problemi.

BTW - il nostro server ha bisogno di alimentare Parse in pochi secondi, non in microsecondi, anche se le prestazioni al secondo posto non farebbero male!

Il nostro attuale sistema di produzione utilizzava lock () per garantire che la lettura / scrittura rimanesse sincronizzata, tuttavia sospettiamo che si verifichino deadlock . Un dato di fatto è che riavviare il servizio elabora la coda dei comandi bene !!

La nostra soluzione proposta è simile a questa:

  private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);

  public async Task ProcessPostedCommandsAsync()
    {
        using (var showContext = new ShowContext())
        {
            var retryCnt = 0;
            var maxRetries = 8;


            var commands = FindCommands(showContext);

            foreach (var command in commands)
            {

                await _mutex.WaitAsync();
                try
                {
                     // read data from the cloud, 
                     // based on data from the cloud, write back to the cloud
                     // mark the command as processed in the database
                     await ProcessCommandAsync(showContext, command);
                }
                catch (Exception e)
                {
                    GlobalLogger.Logger.Error(e,
                        $"assinging executing task, Exception in Process Commands pkid = {command.Pkid} ");
                    GlobalLogger.Logger.Error($"Retry count = {retryCnt}");
                    retryCnt++;
                    await Task.Delay(2000);
                    if (retryCnt == maxRetries)
                    {
                        break; // from for
                    }
                }
                finally
                {
                    _mutex.Release();

                }
            }
        }
    }

Da allora abbiamo modificato il nostro codice per risolvere il problema del deadlock utilizzando il SemaphoreSlim.

Con un solo comando nel database, chiamiamolo Command-A, sembra che la duplicazione si verifichi quando il ciclo di lettura dei comandi rilegge Command-A anche se Command-A ha già dato il via al suo read / write cloud.

Ovunque guardiamo dice: "NON BLOCCA o si bloccheranno" e dice "async" / attendi fino in fondo. Ma se non blocchiamo in qualche modo otterremo condizioni di gara e duplicati.

Quindi abbiamo aggiunto i blocchi e otteniamo ciò che sembra un deadlock dopo ore di elaborazione dei comandi.

Questo sembra un caso d'uso piuttosto comune.

1) Esiste un idioma c # che manca qui che scorre in un elenco di comandi per scrivere in modo asincrono nel cloud ma in ordine sequenziale senza inviare duplicati e non creare deadlock? Vogliamo solo un modo per attendere che ogni comando sincronizzato finisca.

2) Come possiamo testarlo in modo tale da dimostrare che funziona in ambienti diversi.

Ci sentiamo veramente in una situazione Catch 22 . Dobbiamo bloccare per attendere che il comando venga elaborato completamente mentre ancora non dovremmo bloccare il codice asincrono.

Nota: abbiamo esaminato le seguenti risorse e stiamo cercando la direzione fino ad ora.

Concuranza nel C # Cookbook di Stephan Cleary

Async / Attendi best practice

Non bloccare il codice asincrono

    
posta Ray Trask 23.07.2018 - 19:36
fonte

1 risposta

2

Fondamentalmente, le attività asincrone si comportano in modo non molto diverso rispetto ai thread e la maggior parte dell'intuizione del thread dovrebbe essere applicata anche a loro. Ma non si dovrebbero usare le primitive di threading (come il locking) che potrebbero bloccarlo. Se è necessario sincronizzare attività asincrone, è possibile acquisire i blocchi in modo asincrono: link

In realtà, se ho capito bene, come inizio, puoi eseguire tutto il tuo codice come una singola attività:

public async Task StartProcessing()
{
    DisableStartProcessingUI();
    try {
        foreach (result in results) {
            if(TryFind(result, out Document document)) {
                await Update(document);
            } else {
                await CreateDocumentFromResulit(result);
            }
        }
    } finally {
        EnableStartProcessingUI();
    }
}

Questo funziona fondamentalmente in modo sincrono e blocca l'interfaccia utente che viene utilizzata per avviare l'elaborazione, in modo da non eseguirla di nuovo mentre la prima richiesta è ancora in esecuzione. Quindi non devi nemmeno bloccarlo in questo caso. Ma se vuoi utilizzare le richieste simultanee a Update() e CreateDocumentFromResulit() , devi utilizzare alcuni semafori come sopra per evitare di accedere agli stessi documenti.

Modifica : nel tuo esempio, probabilmente dovrebbe essere " attendere ProcessCommandAsync (showContext, comando);"

    
risposta data 24.07.2018 - 06:04
fonte

Leggi altre domande sui tag