Don't use BlockingCollection<T> in Blazor WebAssembly
Published:
There are rare cases when things working in one place won’t work in another. You’ll scratch your head as to why?. You start scouring the web in search of a solution, you obsessively read things about the problematic thing, and ultimately you resort to reading the documentation. Tacit things are the worst; nobody talks about them; people rarely use one or another class you’re using; and, aside from you using your intuition, people writing documentation don’t bother explaining why exceptions exist and what the alternatives are.
Nice non-specific introduction aside, my problem comes from a bigger context:
I am writing a Teamviewer clone, and its desktop frontend runs on Windows Presentation Foundation (WPF). (Disclaimer ahead: I’m a novice. If you want to help, email me.) I take screenshots and send them via asynchronous channels through SignalR1. The WPF client works with two threads: a transfer and a render thread. They are bound by a thread-safe queue, BlockingCollection<T>: the first thread receives the frame and adds it to the queue; the second thread dequeues a frame and renders it.
Working with .NET is almost a dream come true. The next step after finishing the desktop client was to create a web client responsible only for controlling the other peer’s screen2. For me, Blazor WebAssembly is probably the only frontend framework I can tolerate working with. It allows you to use C# code instead of JavaScript for most of your typical tasks related to writing webpages. As such, porting my code from the WPF client should be as easy as copy-paste, right? It would be indeed if it weren’t for a bug in the rendering thread. The receiving thread queued frames like a champ, but the rendering thread unexplicably got stuck at dequeuing them. What was going on?
The cause was for sure BlockingCollection<T>
’s dequeue method, but why? I’ve spoken with some friends for advice, and after some time somebody spotted in the documentation this pesky attribute:
[System.Runtime.Versioning.UnsupportedOSPlatform(“browser”)]3
I indeed don’t look at classes’ attributes and inheritance chains, but this one made my day. And, circling back, there is no justification as to why it’s unsupported, nor is there any information on the web or people discussing this oddity, apart from a minimalistic pull request on GitHub that gave an alternative, BufferBlock<T>
.
Notes
The only way to send streams of data in SignalR is via asynchronous channels that work in a very weird way. On the sharing side, you transmit your byte stream to the SignalR hub without problems, you call a hub’s method and send a .NET Channel object as a parameter. The problem arises when you want to stream that data from the hub to the receiving end. You can’t. The receiving end must also call a hub’s method with a Channel object for the SignalR hub to send data back. So the only solution to this is for the sender to send their data, the hub stores the data in a queue, and the receiver must call another hub method to dequeue and download the data. Before sending streams of data, the simplest way to use SignalR is via callback methods that you register when you connect to the hub: the sender calls a method in the hub, and the hub can easily and asynchronously call the registered method on the receiving client. Working with Channels completely breaks this pattern, and I find it very annoying. ↩
The way Teamviewer apps work is like this: the person sharing their screen must have a client able to stream a video feed of their screen and be able to access an OS-specific API for controlling the mouse and keyboard; the person controlling the peer’s screen must only be able to display the video feed and register mouse and keyboard events. Browsers don’t allow access to OS APIs, so you can’t be the peer that shares their screen (actually, browsers let you stream a video feed, but they don’t allow OS API access); instead, you can be the peer that controls somebody’s screen since browsers allow capturing mouse and keyboard events. ↩
Meaning Blazor WebAssembly, the only “platform” that runs in the browser. There is another flavour of Blazor: “Blazor Web App” that renders pages on the server, so the C# code runs too on the server, not inside the browser. ↩