3

I am confused as to how best handle this situation. I wan't to await the response of an asynchronous call. Specifically I have:

public async Task<IApiData> FetchQueuedApiAsync(TimeSpan receiveTimeout)
{
    var message = await Task.Factory.FromAsync<Message>(
        ReadQueue.BeginReceive(receiveTimeout), ReadQueue.EndReceive);
    return message.Body as IApiData;
 }

This works as expected. However, if the timeout expires then ReadQueue.EndReceive must not be called else it will throw an exception. Obviously FromAsync doesn't know this and calls it blindly - firing the exception. The requirement not to call EndReceive on timeouts is implemented by MSMQ and beyond my control.

The queue exposes an event when a message is ready or a timeout occurs. This is where you would normally check if a message was present, and THEN call EndReceive as per the documentation. Standard listener....

ReadQueue.ReceiveCompleted += ReadQueue_ReceiveCompleted;

My question is that I don't understand how to do that and still have this as awaitable (i.e. I want to be able to await like I have now, but handle it via the delegate). Does that make sense?

AndySavage
  • 1,729
  • 1
  • 20
  • 34

1 Answers1

9

As a rule when you want to convert some non-task based asynchronous model to a task based asynchronous model, if there isn't already a Task method to do the conversion for you, you use a TaskCompletionSource to handle each of the cases "manually":

public Task<IApiData> FetchQueuedApiAsync(TimeSpan receiveTimeout)
{
    var tcs = new TaskCompletionSource<IApiData>();

    ReadQueue.BeginReceive(receiveTimeout);
    ReadQueue.ReceiveCompleted += (sender, args) =>
    {
        if (timedOut)
            tcs.TrySetCanceled();
        else if (threwException)
            tcs.TrySetException(exception);
        else
            tcs.TrySetResult(ReadQueue.EndReceive() as IApiData);
    };
    return tcs.Task;
}

You'll need to adjust this based on the specifics of the types you didn't show, but this is the general idea.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • I think this looks like what I want. I am unsure how the event listener works here. Is that adding a new handler to ReceiveCompleted every time the function is called? Given that this is called lots, do I need to clean that up somewhere? The ReadQueue is also shared between a few concurrent threads. If multiple threads are listening to ReceiveCompleted do they all get the event raised to them? – AndySavage Nov 27 '13 at 21:43
  • @AndySavage I can't really say without knowing more about the specific class you're dealing with. Ideally you just have one method that will be called when the asynchronous event is completed. If the event is shared, then you need to figure out if the completed event corresponds to "this" request. You'll also want to remove the handler when you complete the task, yes. – Servy Nov 27 '13 at 21:58
  • Or, the alternate way is to permanently attach one handler to the receive queue, whose job is to figure out which task is responsible and set it properly. – AnotherParker Jul 18 '14 at 23:40