2013-04-19 15 views
10

Voglio solo ricevere il mio messaggio in un metodo asincrono! e il suo congelamento mia UIMessageQueue e Async/Await

public async void ProcessMessages() 
    { 
     MessageQueue MyMessageQueue = new MessageQueue(@".\private$\MyTransactionalQueue"); 
     MyMessageQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); 

     while (true) 
     { 
      MessageQueueTransaction MessageQueueTransaction = new MessageQueueTransaction(); 
      MessageQueueTransaction.Begin(); 

      ContainError = false; 
      ProcessPanel.SetWaiting(); 

      string Body = MyMessageQueue.Receive(MessageQueueTransaction).Body.ToString(); 

      //Do some process with body string. 

      MessageQueueTransaction.Commit(); 
     } 
    } 

sto solo chiamando il metodo come qualsiasi metodo regolare e le sue nos a lavorare! Questo codice funzionava quando stavo usando BackgroundWorkers invece di async/await

Idee?

+2

Il codice genera avvisi? Hai letto quello che dicono e hai cercato di capirli? – svick

+0

Cerca nell'elenco dei tuoi errori, vedrai "Questo metodo asincrono manca di 'attendi' agli operatori e verrà eseguito in modo sincrono" –

+1

Questa non è una domanda localizzata -inddice, ci sono molte domande simili in SO dove la gente pensa che 'async' in qualche modo rende il loro metodo eseguito in modo asincrono –

risposta

24

Come scrive Stefano, asincrona non viene eseguito il codice in un thread. Fortunatamente, è possibile utilizzare TaskFactory.FromAsync con MessageQueue.BeginReceive /MessageQueue.EndReceive di ricevere messaggi in modo asincrono:

private async Task<Message> MyAsyncReceive() 
    { 
     MessageQueue queue=new MessageQueue(); 
     ... 
     var message=await Task.Factory.FromAsync<Message>(
          queue.BeginReceive(), 
          queue.EndReceive); 

     return message; 

    } 

Si dovrebbe notare però che non c'è una versione di BeginReceive che utilizza una transazione. Da documenti BeginReceive:

Non utilizzare la chiamata asincrona BeginReceive con le transazioni. Se si desidera eseguire un'operazione asincrona transazionale, chiamare BeginPeek e inserire la transazione e il metodo di ricezione (sincrono) all'interno del gestore eventi creato per l'operazione di visualizzazione.

Questo ha senso in quanto non vi è alcuna garanzia per quanto tempo è necessario attendere una risposta o quale thread gestirà la chiamata completata.

di utilizzare le transazioni si potrebbe scrivere qualcosa di simile:

private async Task<Message> MyAsyncReceive() 
    { 
     var queue=new MessageQueue(); 

     var message=await Task.Factory.FromAsync<Message>(queue.BeginPeek(),queue.EndPeek); 

     using (var tx = new MessageQueueTransaction()) 
     { 
      tx.Begin(); 

      //Someone may have taken the last message, don't wait forever 
      //Use a smaller timeout if the queue is local 
      message=queue.Receive(TimeSpan.FromSeconds(1), tx); 
      //Process the results inside a transaction 
      tx.Commit(); 
     } 
     return message; 
    } 

UPDATE

Come Rob ha sottolineato, il codice originale utilizzato il message tornato da Peek, che può aver cambiato tra Peek e Receive. In questo caso il secondo messaggio verrà perso.

C'è comunque una possibilità di blocco, se un altro client legge l'ultimo messaggio in coda. Per evitare ciò, Receive dovrebbe avere un piccolo timeout.

+1

Questa risposta è fantastica! e funziona! Se voglio eseguire questo metodo MyAsyncReceive più volte condivideranno il mio thread? Quindi saranno lenti? – Fraga

+1

Non c'è niente di speciale nel metodo. Il runtime utilizzerà un thread disponibile dal threadpool per ogni chiamata. –

+0

Questo non contiene una condizione di gara? La variabile 'message' può contenere un messaggio diverso da quello ricevuto, vero? (Soprattutto se ci sono più di un consumatore). Il peek restituirà un messaggio * a * ma il metodo 'Receive()' potrebbe riceverne uno diverso. Vorrei rilasciare il 'var message =' nel 'Peek awaiter' e usare 'var message = queue.Receive (tx);' per assicurarci che il messaggio corretto sia elaborato nella transazione .. – RobIII

6

async does not run your code on a background thread. Il tuo codice sopra dovrebbe aver causato un avvertimento del compilatore che ti dice che il tuo metodo verrà eseguito in modo sincrono.

Se si desidera eseguire un metodo su un thread in background, utilizzare TaskEx.Run:

public void ProcessMessages() 
{ 
    ... 
} 

TaskEx.Run(() => ProcessMessages()); 
+0

Se utilizzo run così creerà un nuovo thread! quindi non posso modificare l'UI direttamente, dovrò usare Dispatch rigth? come i fili ordinari? O c'è un altro modo? Ho solo bisogno di aggiornare una barra di avanzamento che si trova nel modulo principale. – Fraga

+0

Io raccomando di usare 'IProgress ' e 'Progresso ' che gestisce il dispacciamento dei thread per te. La [documentazione di TAP copre 'IProgress '] (http://msdn.microsoft.com/en-us/library/hh873175.aspx). –

+0

Non posso contrassegnare due risposte come giusto! La risposta alla mia domanda sta usando Task.Factory.FromAsync ma in questo modo non userò la potenza dei nuovi thread. Quindi userò Run! Grazie – Fraga

Problemi correlati