MessageQueue и 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

Идеи?

Ответ 1

Как пишет Стивен, async не запускает ваш код в потоке. К счастью, вы можете использовать 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 должен иметь небольшой тайм-аут.

Ответ 2

async не запускает ваш код в фоновом потоке. Ваш код, приведенный выше, должен был вызвать предупреждение компилятора, указывающее, что ваш метод будет работать синхронно.

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

public void ProcessMessages()
{
  ...
}

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