Qual è il modo migliore per attendere un pacchetto di rete utilizzando la nuova funzione Async di C#

StackOverflow https://stackoverflow.com/questions/7393196

Domanda

Di recente ho giocato con il nuovo CTP Async e mi sono imbattuto in una situazione in cui non sono sicuro di come procedere.

Nella mia attuale base di codice, sto usando un concetto di "posti di lavoro" e un "gestore di lavoro". I lavori esistono esclusivamente allo scopo di gestire un messaggio iniziale, di inviare una risposta e quindi di aspettare la risposta.

Ho già un codice esistente in base alle prese sincroni, in cui un thread di rete sta aspettando l'arrivo dei dati, quindi passandolo a un gestore di eventi e infine al gestore del lavoro.

Il gestore del lavoro cerca quale lavoro gestirebbe il messaggio e lo trasmette.

Quindi lo scenario è questo:

  1. Il gestore del lavoro riceve un nuovo messaggio e lancia un lavoro.
  2. Il lavoro inizia, elabora il messaggio e invia un messaggio di risposta.
  3. A questo punto il lavoro aspetterebbe una risposta alla risposta.

Ecco un esempio di pseudocodi:

class MyJob : Job
{
    public override void RunJob( IPacketMsg packet )
    {
        // handle packet

        var myReply = new Packet();
        SendReply( myReply );

        await GetResponse();
    }
}

Ma non sono del tutto sicuro di come procedere al passaggio 3. Il gestore del lavoro riceverà la risposta e poi la consegnerà al lavoro in esecuzione. Ma non sono sicuro di come far aspettare il lavoro per la risposta.

Ho pensato di creare un compito atteso che blocca semplicemente su un waithandle, ma è questa la soluzione migliore?

Ci sono altre cose che potrei fare in questo caso?

ModificareSul tema dell'asincronazione CTP, ciò che accade in una situazione in cui l'interfaccia utente non viene utilizzata. Ho letto il blog Async di Eric Lippert, ma non credo che abbia mai toccato l'argomento di come tutto funziona in background senza un thread dell'interfaccia utente (fa girare un lavoratore di fondo o ...?)

È stato utile?

Soluzione

  1. Il gestore del lavoro riceve un nuovo messaggio e lancia un lavoro.
  2. Il lavoro inizia, elabora il messaggio e invia un messaggio di risposta.
  3. A questo punto il lavoro aspetterebbe una risposta alla risposta.

Prima di tutto, dovrei menzionare che il CTP asincrone gestisce Operazioni asincroni Molto bene, ma Eventi asincroni Non così tanto. Potresti considerare un approccio basato su RX. Ma procediamo per il momento con il CTP asincrero.

Hai due opzioni di base per creare attività:

  • Con un delegato. per esempio, Task.Factory.StartNew eseguirà un delegato sul pool di thread. Le fabbriche di attività personalizzate e gli programmi offrono più opzioni per i delegati delle attività (ad esempio, la specifica del delegato deve essere eseguita su un thread STA).
  • Senza un delegato. per esempio, TaskFactory.FromAsync avvolge un esistente Begin/End Coppia di metodi, TaskEx.FromResult restituisce una "costante futura", e TaskCompletionSource può essere usato per controllare a Task esplicitamente (entrambi FromAsync e FromResult uso TCS internamente).

Se l'elaborazione del lavoro è rilegata dalla CPU, ha senso passarlo a Task.Factory.StartNew. Suppongo che l'elaborazione del lavoro sia legato alla CPU.

PSEUDO-CODE GESTIONE GIOB:

// Responds to a new message by starting a new job on the thread pool.
private void RespondToNewMessage(IPacketMsg message)
{
  IJob job = ..;
  Task.Factory.StartNew(job.RunJob(message));
}

// Holds tasks waiting for a response.
private ConcurrentDictionary<int, TaskCompletionSource<IResponse>> responseTasks = ..;

// Asynchronously gets a response for the specified reply.
public Task<IResponse> GetResponseForReplyAsync(int replyId)
{
  var tcs = new TaskCompletionSource<IResponse>();
  responseTasks.Add(replyId, tcs);
  return tcs.Task;
}

// Responds to a new response by completing and removing its task.
private void RespondToResponse(IResponse response)
{
  var tcs = responseTasks[response.ReplyId];
  responseTasks.Remove(response.ReplyId);
  tcs.TrySetComplete(response);
}

L'idea è che il gestore del lavoro gestisce anche un elenco di risposte di estromesso. Affinché ciò accada, ho introdotto un semplice int Rispondi Identificatore che il gestore del lavoro può utilizzare per determinare quale risposta va con quale risposta.

Ora i lavori possono funzionare in questo modo:

public override void RunJob(IPacketMsg packet)
{
  // handle packet
  var myReply = new Packet();
  var response = jobManager.GetResponseForReplyAsync(myReply.ReplyId);
  SendReply(myReply);

  await response;
}

Ci sono alcune cose difficili da quando stiamo mettendo i lavori sul thread del pool di thread:

  1. GetResponseForReplyAsync deve essere invocato (Registrazione dell'attività) prima che la risposta venga inviata ed è quindi awaitEd più tardi. Questo per evitare la situazione in cui una risposta può essere inviata e una risposta ricevuta prima di avere la possibilità di registrarci.
  2. RespondToResponse Rimuoverà la registrazione dell'attività prima di completarla, nel caso in cui il completamento dell'attività provoca un'altra risposta con lo stesso ID.

Se i lavori sono abbastanza brevi da non dover essere posizionati sul thread del pool di thread, la soluzione può essere semplificata.

Altri suggerimenti

Sul tema dell'asincronazione CTP, ciò che accade in una situazione in cui l'interfaccia utente non viene utilizzata. Ho letto il blog Async di Eric Lippert, ma non credo che abbia mai toccato l'argomento di come tutto funziona in background senza un thread dell'interfaccia utente (fa girare un lavoratore di fondo o ...?)

await tornerà al suo contesto di sincronizzazione. In un processo dell'interfaccia utente, questo è un ciclo di messaggi dell'interfaccia utente. In ASP.NET, questo è il pool di thread ASP.NET. In altre situazioni (applicazioni di console e servizi Win32), non esiste un contesto, quindi le continue vengono messe in coda al ThreadPool. Questo di solito non è un comportamento desiderato, quindi ho scritto un AsyncContext Classe che può essere utilizzata in quelle situazioni.

BackgroundWorker non è usato. In uno scenario lato server come il tuo, non è raro non avere affatto un thread di fondo.

Avresti semplicemente il resto del tuo gestore di eventi con il modello di attesa come così:

 public async void RunJob(IPacketMsg msg)
 {
     // Do Stuff

     var response = await GetResponse();

     // response is "string", not "Task<string>"

     // Do More Stuff
 }

 public Task<string> GetResponse()
 {
     return Task.Factory.StartNew(() =>
        {
             _networkThingy.WaitForDataAvailable();

             return _networkThingy.ResponseString;
        });
 }

Quando il compito di Ottieni risposta termina, il resto del metodo raccoglie l'esecuzione sul contesto di sincronizzazione corrente. Fino ad allora, tuttavia, viene fornita l'esecuzione del metodo (quindi qualsiasi codice dopo l'attesa non viene eseguito fino a quando l'attività è iniziata nelle finiture getResponse)

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