This project has moved and is read-only. For the latest updates, please go here.

AsyncCollection Timeout?

Sep 2, 2014 at 6:06 PM
Is there an easy way to reproduce this functionality from the BlockingCollection class, or am I stuck creating a CancellationTokenSource and calling CancelAfter every time I want to retrieve an item from this collection?
Sep 2, 2014 at 8:47 PM
Edited Sep 2, 2014 at 9:00 PM
AsyncEx does not provide specific support for timeouts, but it's pretty easy to create an extension method:
public static async Task<T> TakeAsync<T>(this BlockingCollection<T> collection, TimeSpan timeout)
  using (var cts = new CancellationTokenSource())
    return await collection.TakeAsync(cts.Token);
I decided not to support timeouts for a few reasons.

Timeout behavior can be easily achieved by the built-in CancellationTokenSource.

Timeout overloads add geometrically to the API (especially if I follow the BCL standard practice of supporting both TimeSpan and int parameters). Also, the parameter types do not imply "timeout" behavior; neither int nor TimeSpan imply a timeout.

Adding timeout as a separate result condition complicates the API and/or handling. An overload that takes both a TimeSpan and a CancellationToken should respond in different ways depending on whether the timeout was triggered or the operation was cancelled. Different types in the BCL handle this differently, but the most common approach is to change the method signature (e.g., return a bool and set an out param). While this makes sense for synchronous APIs, it is no good for async APIs (an async method cannot have an out param). One alternative is to change the method signature in a more functional way (e.g., Tuple), but the language support for that is weak. An alternative would be two different exception types: OperationCanceledException and TimeoutException.

But the primary reason I don't support timeouts is because the underlying APIs do not naturally support timeouts. In the case of synchronous thread coordination primitives, the Win32 API has timeout support built-in. So, the timeout values are just passed on to the Win32 API. In the case of asynchronous primitives, there is no Win32 API, so there's no simplicity/efficiency gained when adding a timeout parameter like there is for the synchronous primitives.
Sep 2, 2014 at 8:52 PM
Edited Sep 2, 2014 at 9:20 PM
Thanks. That helps quite a bit, though are you sure you don't mean AsyncCollection<T>? It isn't a subclass of BlockingCollection<T>

In my case, since there is no real ability to "Cancel" the operation (I'm using solely the timeout functionality in this case), the only reason for a task cancellation will be the timeout, so I may just end up catching the cancellation exception and throwing a timeout exception instead.
Sep 3, 2014 at 12:48 AM
Yes, I did mean AsyncCollection<T>, not BlockingCollection<T>. :)
Sep 3, 2014 at 7:26 PM
So I tried this, and there's a bit of an issue. Your library uses exceptions to signal cancellation. While this works, and is arguably the "correct" way to do things, it has significant overhead and causes a ton of slowdown if there are a lot of cancellations, or in this case timeouts. Since throwing exceptions every 200ms isn't exactly streamlined, I was forced to fall back onto using Task.Run to run a BlockingCollection<T> call, which I realize probably isn't doing exactly what I think is doing but still seems to function correctly. Are there any other ways to deal with this? I'm hesitant to start mutilating your library to support non-throwing cancellation.
Sep 4, 2014 at 3:58 AM
I'm not really sure of your use case, here.

Under what conditions do you want to retrieve an item with a timeout? It seems to me that there may be a better way to approach the problem.
Sep 4, 2014 at 10:04 AM
It's a little hard to explain exactly. I've got data objects coming in from a serial port (they get tokenized and converted first, obviously), and I have a gating mechanism for waiting for objects with specific properties to trigger (An ACK signal, for instance). The issue is that the gating mechanism borks up the UI, since it can be anywhere from 20ms to Infinity for the device to reply. I built up an entire device library centered around this mechanism, so nearly all of it uses awaits to update without blocking the UI.

Here's the general idea for the mechanism (This version isn't quite right)

Originally a similar mechanism was used that used AutoResetEvents and a long-running background thread, but there were some fairly large issues with that, not least of all it's resemblance to monolithic barely-maintainable state machine and major binding problems.
Sep 4, 2014 at 2:38 PM
So, if I understand you correctly, the serial device response is optional, but should arrive within 200ms if present...

It may be cleaner to approach the serial device by modeling all reads as a stream. Have you considered Rx?

P.S. On a side note, modern exceptions are extremely fast. Have you identified this as a performance problem using profiling? On my machine, I'm seeing 25-30 exceptions raised by CancellationTokens every 200ms, so <10ms each. One every 200ms would only be <50ms per second on your UI thread.
Sep 10, 2014 at 7:47 AM
To jump in here: I must second the Rx idea. You have allot of powerful extensions in the framework and I am using it for exactly the same reason: serial communications and also use it to calculate/handle time-outs. It took me a while to get my head around it, but I'm loving it now. So much that all streams I now use are wrapped in an Rx Observable sequence. I can unfortunately not share any code as this is IP of my company I work for, but there is allot of examples on the internet.
Sep 10, 2014 at 1:28 PM
One of my Eventual Goals is to rewrite my old TCP/IP communications library to use Rx. I'm now wondering if it would be possible to do something similar with serial ports...
Sep 10, 2014 at 2:20 PM
StephenCleary wrote:
One of my Eventual Goals is to rewrite my old TCP/IP communications library to use Rx. I'm now wondering if it would be possible to do something similar with serial ports...
Anything that can be seen as a stream will work in Rx like a charm, so definitely serial comms. We have done it (but as I previously said I may not publish it, maybe if I re-write it in another library...)
Sep 17, 2014 at 7:00 PM
The Rx Library worked like a charm, and was basically exactly what I was looking for. It made the Serial Port wrapper I have much more usable and flexible, and really helped with some of the event subscription logic for monitoring components and UI updates thanks to NotifyOn. I'm working on converting the rest of my coms chain to use it rather than standard events.

It's definitely possible to use Rx to wrap a serial port. My current wrapper doesn't mirror the underlying interface, so it's not really suitable for other people to use (also I suspect my company would be somewhat annoyed if I released it), but I don't see any reason why it wouldn't be possible. The Rx Library looks to be very useful for any high-latency communications. I'm actually kind of surprised it's not included by default.

I also ended up using your AsyncLock once to control access to the communications interface. Very useful.
Sep 17, 2014 at 8:13 PM

I always try to steer people to Rx for "asynchronous streams". Unfortunately, a lot of people are scared away from the Rx learning curve (especially when teams are involved).

Rx is released out-of-band because they prefer a more aggressive release schedule. The core Rx interfaces have been moved into the BCL, but the library itself prefers to remain separate. Also, they're able to support a very wide range of platforms.

P.S. I suspect you mean ObserveOn. :)
Sep 18, 2014 at 10:24 AM
Rx is a cool concept. I'd think it would just make things easier to understand, since event subscription isn't exactly intuitive for most people anyway.

I actually meant SubscribeOn, but that's in reference to UI Components registering their observers with the ports observable, not really with the internal workings of the ComPort wrapper (Which ended up being subjects for various reasons).

I'd definitely be interested in seeing another Serial Port wrapper or subclass. Most of the ones I looked at for reference weren't particularly sophisticated, and quite a few were incomplete or had major bugs. It seems like Serial Ports don't get a ton of love nowadays, which is a shame since the only other generic data connector is USB or Ethernet.
Sep 18, 2014 at 12:55 PM
No kidding. I remember finding one bug in SerialPort where it refused to open any serial port unless its name was "COMx". This was problematic for com0com and some USB-to-serial adapters (depending on how their drivers were written) - for one project I had to p/Invoke to D2XX because of that BCL bug. :/

Maybe I should start with a "SerialPort done right" library. :)
Sep 18, 2014 at 8:29 PM
Lol, yeah, I ran into that "Bug" too. Very, very annoying. You'd think they'd at least support NULL as well. Always useful to have a null device, and it's crucial for good UI design (My UI automatically attempts to open the selected port, so having a "No Port" option is required). The GetPortNames() is a joke as well. I didn't even realize it would return invalid port names until I read your issue. It's also not guaranteed to be up to date or even get all ports with a COMxxx format, which makes testing for removal or attachments difficult. That's straight up terrible design. I ended up using WMI to query Win32_PnPEntitys with "%COM%". I had a friend who swears by P/Invoking SetupDi stuff, but I thought that was a bit much.

As for drivers...Microsoft kind of screwed over most open source driver projects like com0com over with their somewhat overzealous driver signing program. Shelling out cash every year is annoying or impossible for developers, and disabling driver signing entirely is difficult and dangerous for the users.

This discussion is getting a bit off the stated topic though. I don't want to clutter up your issues section with semi-relevant chitchat. Might we move to email, or at least to the discussions page?
Sep 19, 2014 at 1:05 AM
Actually, this is the discussions tab. :)