2

I have to write some middle-ware code that requires me to return a Task object. My middle-ware code also uses a framework that asynchronously sends data to a connected client, returning a Task.

Pretty straight forward so far.

However my use case is that I have many connected clients. So my question is basically, what is the most sensible way to call each client's async Send method, and more importantly, what is the Task I should be returning. I'm using c# 5.

class Client // some framework
{
    public Task Send(Object message)
    {
        // does its thing somehow, returns a Task
    }
}


interface Handler<TMessage> // some framework
{
    Task Handle(TMessage msg);
}

class Middleware : Handler<Object> // my middleware implementation code
{
    List<Client> clients;

    public Task Handle(Object msg)
    {
        //i do some stuff here before calling each client's Send method

        return Task.Run(() => clients.ForEach(client => client.Send(msg))); <-- my current solution
    }
}

So in short, I receive a message (the handler), I perform some custom logic (filtering clients etc) and then call each clients async Send method. Depending on the nature of the custom logic which will vary from case to case, I may want to wrap that code inside the Task also. Any advice on this would also be greatly appreciated.

My confusion is, should I be returning the outer Task that I've created as is? Or is there some other preferred way of executing each async method that I'm not aware of.

EDIT:

The Send method is asynchronous even though it's not named SendAsync. Keep in mind it is a framework call, so I can't rename it to SendAsync as suggested. However, rest assured that it is an asynchronous call under the covers, and returns a Task.

Given the truly asynchronous nature of the call, is my usage of:

return Task.Run(() => clients.ForEach(client => client.Send(msg)));

going to cause any issues, given I don't want to wait for each client's asynchronous Send() method to run to completion?

To clarify: by 'cause any issues', I mean is there any cause for concern in simply executing the above? Deadlocks, Exceptions that go unobserved etc..? I ask because I know there are a lot of subtleties in asynchronous programming that aren't always obvious, that someone with a better understanding than me might be able to point out as a possible issue, or perhaps know of a better way to achieve what I'm aiming for...

AaronHS
  • 1,334
  • 12
  • 29
  • How about sending using Parallel Task Library Task.Factory.StartNew( () => Parallel.ForEach(clients, client=> client.Send(msg))); There is no need to return task in send method in this case. – Access Denied Dec 08 '15 at 05:26
  • Not sure what you mean by 'there is no need to return task in send method'. My code isn't returning the task from the send method(s). It is returning the outer task, just as yours is. The only difference is your inner foreach is done in parallel, which is not the question I'm asking. – AaronHS Dec 08 '15 at 05:33
  • How about this thread http://stackoverflow.com/questions/12144077/async-await-when-to-return-a-task-vs-void? – Access Denied Dec 08 '15 at 05:33
  • @AccessDenied the question clearly states that my code is a middleware between two async frameworks. Not sure what that link has to do with anything I've asked. – AaronHS Dec 08 '15 at 05:35

1 Answers1

4

Since the Send() method is asynchronous and returns a Task, you should do something like this:

public Task Handle(Object msg)
{
    return Task.WhenAll(clients.Select(client => client.Send(msg)));
}

The WhenAll() method will enumerate the sequence of Task objects (projected from the original client values), and return a Task object that represents the completion of all of the client Task objects that were returned.


You should consider changing (or encourage the owner of that code to change) the Send() method's name so that it includes the word Async in it (e.g. SendAsync()). Obviously, the compiler doesn't care what you call the method, but the convention is very useful for easy understanding of asynchronously-based code.


ADDENDUM:

From your revision to the question:

given I don't want to wait for each client's asynchronous Send() method to run to completion?

If you don't want to wait for each client's completion, then just start each operation. There's no reason to use a Task to start the operations:

public void Handle(Object msg)
{
    //i do some stuff here before calling each client's Send method

    clients.ForEach(client => client.Send(msg));
}

By definition, an asynchronous method returns before it's completed. By convention, it will do so quickly. So there should be no need to wrap the execution of the ForEach() in a Task, nor any need for Handle() itself to be asynchronous at all (at least, not for this particular statement in the method).

I mean is there any cause for concern in simply executing the above? Deadlocks, Exceptions that go unobserved etc..?

Certainly you will fail to observe exceptions that occur, at least based on the code you've provided so far.

As for deadlocks, there's no way to know if your code might suffer from that; there's nothing about starting asynchronous operations that in and of itself would create a deadlock scenario, nor would "fire and forget" as you do here add any particular risk of deadlock (if anything, it might reduce the risk, since if you aren't capturing the Task for each operation at all, you obviously can't be synchronously blocking on any of them).

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • 1
    Also, I don't need to add the async modifier to the framework's Send method in order to make it asynchronous, or to make it awaitable (like I could anyway given it's not my code). It is asynchronous (or at least some part of it is) by virtue of the fact that it returns a Task https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx , and will *most likely* run asynchronously. There is literally nothing unclear here, apart from the lack of convention in the name of the method for which I have no control over, and nevertheless stated clearly was asynchronous anyways – AaronHS Dec 08 '15 at 11:33
  • 1
    @AaronHS I agree with all of these comments yet this answers the question, does it not? Also you have to understand that a lot of questions are based on some confusion. For that reason I, too, would have added a few remarks about the missing Async name postfix. Just make sure that the OP is not confused. – usr Dec 08 '15 at 12:05
  • @PeterDuniho Regarding your answer, I'm not sure I follow.. The Handler interface returns a Task (note I marked the interface as a framework's interface in my OP), therefore my middleware impl cant change to a void. Also, even if i could, why would I want to? If I have 10k connected clients, why would I want to wait for the enumeration to complete? The entire pipeline i'm injecting my middleware into is asynchronous (up until some point in the framework where there must be an await) so why would I want to change that even if I could? Seems your original answer is the best answer, is it not? – AaronHS Dec 08 '15 at 22:19
  • _"Seems your original answer is the best answer, is it not"_ -- yes, I see. If you must match a specific interface signature, you'll have to return a `Task`. But then the question is, what is that `Task` supposed to represent? The interface presumably comes with a specification of what the `Task` means, but I didn't see that description in your question. If the `Task` is to mean completion of all the send operations, then my first suggestion is appropriate. – Peter Duniho Dec 08 '15 at 22:26
  • It shouldn't take long to just initiate 10K async operations, but if it does for some reason, you could in fact go with what you originally had (i.e. put the `ForEach()` in its own task). But then the `Task` returned would represent completion not of the operations themselves, but just their initiation. Is that what the interface requires you to do? The interface declaration itself doesn't really explain the _intent_ behind the method, so I'm not able to make any sort of definitive statement as to what `Task` object should be returned. I do agree you have to return _some_ `Task` object though. – Peter Duniho Dec 08 '15 at 22:27
  • The calling framework is a pipes and filters pattern for delivery of messages. The point that I'm injecting into is basically at the end of the delivery chain. So i'm guessing the intent of the Task would be to represent my consumption of the message (whatever that entails). But in my case I'm forwarding it on to *another* asynchronous list of logical consumers (hence why I refer to it as middleware). Regardless of that though, wouldn't the intent always be to return a Task that represents the initiation of *however I choose to handle the message*? – AaronHS Dec 08 '15 at 22:58
  • _"wouldn't the intent always be to return a Task that represents the initiation of however I choose to handle the message"_ -- In your context, I don't know the intent. Normally a `Task` represents the _completion_ of an operation. If you are writing an intermediary that is supposed to handle a message, and you are delegating that in some way by sending the message to multiple clients, then it seems to me you haven't actually handled the message until all of the clients have. Compare to a general contractor who delegates construction work; he's not done until his subs are done. – Peter Duniho Dec 08 '15 at 23:34
  • Ok, I see what you're saying. I was under the impression that a Task represented an asynchronous operation that could be *awaited* on, or monitored for completion. But I still take your point. I guess it's whether I care if the client got the message that determines whether I have actually *handled* the message. If I don't care, then I have handled it. If I do care, then I should be awaiting completion. Is that a fair summary? – AaronHS Dec 09 '15 at 00:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/97347/discussion-between-peter-duniho-and-aaronhs). – Peter Duniho Dec 09 '15 at 00:11