First I want to thank Matthew Groves for hosting the 1st known C# Advent (English). I was honored to be able to grab the spot for Friday, December 22, 2017, which, happily, is the start of my Christmas holiday week, as well.
The crux of this post is that most visible performance issues in a Windows application come from the presentation layer. Specifically, anything that puts load or wait states on the main "UI Thread" will make the app/application appear to hang or become unresponsive for periods of time. This post talks about strategies for getting load off the UI as much as possible, beyond the async/await mechanism in C#. Most such load can be unloaded to a worker thread fairly easily. Other tasks can be awaited together. In some cases, a UI component is involved, and it becomes necessary to manage load that, for that reason, reason MUST stay on the UI thread.
I remember when I was a kid hearing of projects for stock traders that handled hundreds of data update events every second and being totally intimidated by the thought of it. I knew I'd "come of age" in technology when, in 2017, I worked with a focused team (known as "Blockheads") to build such an app. This latest generation "stock blotter" ran stable, without memory leakage, and with no apparent lag at tens of Gigabytes per second! These general ideas stem back to the project I worked on in 2016-2017 with BlueMetal for Fidelity Investments' Equity Trading team, called Artis OMT. Artis OMT has been on Fidelity's Equity Trading floor for over a year now, and will soon reach a year of full deployment. While Artis OMT was WPF, this post looks at similar performance ideas in a similar but different platform: Windows 10 UWP (store apps).
Artis OMT didn't start out able to handle 90Gigabytes of incoming data. We had to use JetBrains tools to identify code that was bogging down or hanging the main UI thread. That analysis, alone, is perhaps the subject of a different post, or more, some day.
When folks start thinking about UI Thread execution, the first thing most think of is Dispatcher.BeginInvoke(). This method is how you add workload to the UI thread. I'm trying to talk about how to UNLOAD the UI thread, and/or manage your load so that the user won't observe UI freezes or lockups.
The title of this says it all, really. Push a workload off the current thread. Use whenever you have long running processes that you don't have to touch UI controls from. If you have timing dependencies, you can manage them with Task.When, Task,Wait, or even better, Task.ContinueWith(). Examples below cover this a little more.
Service calls are low hanging fruit. So often I see code that makes calls in series, waiting on the results of one before making the next call, even though the two calls have no dependencies on each other... it's just so much easier to write the sequence case that folks let it hang. await Task.WhenAll(...) is not as syntactically sweet, but still MUCH sweeter than having to set up an aggregate event.
/// Total time spent is the sum of all tasks' time.
/// Executes all requests simultaneously, letting the default task dispatcher do its thing.
/// total time spent is no more than the longest running individual task, all other things being equal.
As always, there's some overhead with task switching. You'll notice that the time was just a few ticks longer than 1.5 seconds.
What if you can't unload the UI thread? what if your long running process must interact with controls like a huge grid that needs to calculate an aggregation of a data set that lives in it?
I once scrubbed references to Visual Basic from my CV and landed a job that had scrubbed VB from the job description. I didn't want to work for a company that would hire a "VB-Weenie" and they didn't want to hire a "VB-Weenie", either... but there was VB6 work to do.
One thing that VB6 had going for it was a concept called DoEvents(). It enabled you to give up processing the current method to allow any pending events to execute. It would then return to finish the calling method.
In C#, the closest equivalent, nowadays, is "await Task.Yield()" or await.Task.Delay(...).
Most folks talk about using "await Task.Yield()" at the start of an awaitable method to make sure the whole method runs asynchronously. There's some sense to that. More importantly, one can interrupt long running processes that must run on the UI in order to allow the UI to respond to user inputs. In testing, I've seen that Task.Yield() often doesn't allow enough room for redraws of the UI. Likewise, setting a Task.Delay of a 1 tick timespan isn't enough, either. 1 millisecond delay, however, does seem to suffice in my basic testing.
As always, use this very carefully. This has overhead of its own, as well, that can cause performance issues.... including potential deadlocks.
Just wanted to say thanks for inviting me to reprise my Boston Code Camp 28 presentation for the group last night (12/7). I enjoy talking about development anywhere in the Microsoft stack, and I love that Windows 10 IoT Core + Azure IoT Hub spans a good range of it. I have a bunch of other topics I'd be happy to share, especially in the Windows 10 domain. Check through this blog, I have a post for each that shares slides, but I know the presentation's a big part of the value of them.
These slides are really only a tweak of the BOSCC28 slides with the NEMD group title. This is an embedded Microsoft Office presentation, powered by Office Online.
The group is primarily about Windows 10 app development (implying Universal Windows Platform, UWP). That said, the group's been begging for sessions on AI and related services for some time.
Since I've been helping a client navigate LUIS lately, I figured an into to the LUIS service would be a nice way to break into this very useful service.
I reserve the right to tweak these slides up until I present them on Thursday, November 16th, 2017.