2013-04-19 4 views
10

Я только хочу получить свое сообщение в асинхронном методе! и его замораживание моего UIMessageQueue и 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(); 
     } 
    } 

Я просто называю метод, как любой обычный метод, и его nos работают! Этот код работал, когда я использовал BackgroundWorkers вместо async/await

Идеи?

+2

Является ли ваш код предупреждением? Вы читали то, что говорили и пытались понять их? – svick

+0

Посмотрите в своем списке ошибок, вы увидите: «Этот асинхронный метод не использует« ждут »операторов и будет работать синхронно» –

+1

Это не локализованный вопрос -indeed, есть много подобных вопросов в SO, где люди думают, что 'async' каким-то образом заставляет их запускать асинхронно. –

ответ

24

Как пишет Стивен, асинхронная не работает ваш код в потоке. К счастью, вы можете использовать TaskFactory.FromAsync с MessageQueue.BeginReceive /MessageQueue.EndReceive получать сообщения асинхронно:

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

     return message; 

    } 

Следует отметить, однако, что не существует версия BeginReceive, которая использует транзакции. Из документов BeginReceive:

Не используйте асинхронный вызов BeginReceive с транзакциями. Если вы хотите выполнить транзакционную асинхронную операцию, вызовите BeginPeek и поместите транзакцию и (синхронный) метод Receive в обработчик событий, который вы создаете для операции peek.

Это имеет смысл, поскольку нет никакой гарантии, сколько времени вам нужно ждать ответа или какой поток в конечном итоге будет обрабатывать завершенный звонок.

Чтобы использовать транзакции можно было бы написать что-то вроде этого:

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

Как Роб отметил, оригинальный код используется message вернулся из Peek, который может быть изменен между Peek и Receive. В этом случае второе сообщение будет потеряно.

По-прежнему существует вероятность блокировки, если другой клиент читает последнее сообщение в очереди. Чтобы предотвратить это, Receive должен иметь небольшой тайм-аут.

+1

Этот ответ замечательный! и это работает! Если я хочу запустить этот метод MyAsyncReceive несколько раз, они разделит мой поток? Значит, они будут медленными? – Fraga

+1

В этом методе нет ничего особенного. Время выполнения будет использовать доступный поток из потока thread для каждого вызова. –

+0

Разве это не содержит расовое условие? Переменная 'message' может содержать другое сообщение, кроме полученного, не так ли? (Особенно, если есть более одного потребителя). Peek вернет сообщение * a *, но метод 'Receive()' может получить другой. Я бы бросил 'var message =' в 'Peek awaiter' и использовал 'var message = queue.Receive (tx);' чтобы убедиться, что правильное сообщение обработано в транзакции. – RobIII

6

async does not run your code on a background thread. Ваш код, указанный выше, должен был вызвать предупреждение о компиляторе, указывающее, что ваш метод будет работать синхронно.

Если вы хотите выполнить метод на фоне потока, используйте TaskEx.Run:

public void ProcessMessages() 
{ 
    ... 
} 

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

Если я использую run, он создаст новый поток! поэтому я не могу изменить UI Directly, мне нужно будет использовать диспетчерскую установку? как обычные потоки? Или есть другой способ? Мне просто нужно обновить индикатор выполнения, который находится в главной форме. – Fraga

+0

Я рекомендую использовать 'IProgress ' и 'Прогресс ', который обрабатывает поток для вас. В документации [TAP содержится описание «IProgress '] (http://msdn.microsoft.com/en-us/library/hh873175.aspx). –

+0

Я не могу отметить два ответа как правильно! Ответ на мой вопрос заключается в использовании Task.Factory.FromAsync, но я не буду использовать силу новых потоков. Поэтому я буду использовать Run! Спасибо – Fraga

Смежные вопросы