Friday, December 22, 2017

Unloading the UI Thread in C# on Windows 10 + UWP

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.


Here, however, are a few relatively easy ways to really make use of the extra cores in your CPU, and make your apps appear to perform much better:

Task.Run(() => { ... });
Classic depiction of processes running in sequence vs in parallel

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.



Batch remote service calls using Tasks and WhenAll()

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.



///

/// Does one request at a time, holding up the entire process
/// at each step until it completes. Simpler code but....
/// Total time spent is the sum of all tasks' time.
///
public async void GetContentinSequence(Session session)
{
    var dbContent = await GetDatabaseContent(session);
    var webContent = await GetWebContent(session);
    var userProfile = await GetUserProfile(session);
    var userContext = await GetUserContext(session);
}



///

/// 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.
///
public async void GetContentinParallel(Session session)
{
    var contextTask = GetDatabaseContent(session);
    var webContentTask = GetWebContent(session);
    var userProfileTask = GetUserProfile(session);
    var userContextTask = GetUserProfile(session);

    var stuff = new Task[] { contextTask, webContentTask, userProfileTask, userContextTask };
    await Task.WhenAll(stuff);

    var dbContent = contextTask.Result;
    var webContent = webContentTask.Result;
    var userProfile = userProfileTask.Result;
    var userContext = userContextTask.Result;
}

Here's an example that makes this more clear:


var start = DateTimeOffset.Now;
var task1 = Task.Run(async () => { await Task.Delay(1000); });
var task2 = Task.Run(async () => { await Task.Delay(1500); }); //1.5 seconds
var task3 = Task.Run(async () => { await Task.Delay(1000); });
var task4 = Task.Run(async () => { await Task.Delay(1000); });
var tasks = new Task[] { task1, task2, task3, task4 };
Task.WhenAll(tasks).ContinueWith(t => { Debug.WriteLine(DateTimeOffset.Now - start); });


outputs something like:
00:00:01.5623681

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?   

Here's an option...

DoEvents() erhhh... ummm...  await Task.Delay(...)

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.

private async void LongRunningAggregatorOnUIThread(object sender, object e)

{
    await Task.Yield();
    timer.Stop();
    var timeoutRate = TimeSpan.FromMilliseconds(100);

    
    var timeout = DateTimeOffset.Now.Add(timeoutRate);
    var value = 0L;
    while (true)
    {
        value++;
        if (DateTimeOffset.Now >= timeout)
        {
            textbox.Text = value.ToString();
            await Task.Delay(1);
            timeout = DateTimeOffset.Now.Add(timeoutRate);
        }
    };
}


As always, use this very carefully.  This has overhead of its own, as well, that can cause performance issues.... including potential deadlocks.

11 comments:

anushya said...

the above content you shared is very useful and the way of presentation is easy to understand.
Selenium Training in Bangalore
Selenium Course in Bangalore
AWS Training in Bangalore
Devops Training in Bangalore
Java Training in Bangalore
Data Analytics Training in Bangalore
Digital Marketing Training in Bangalore
Python Course in Bangalore

velraj said...

I am feeling happy to read this. You gave nice info to me. Please update more.
Ethical Hacking course in Chennai
Ethical Hacking Training in Chennai
Hacking course in Chennai
ccna course in Chennai
Salesforce Training in Chennai
AngularJS Training in Chennai
PHP Training in Chennai
Ethical Hacking course in Tambaram
Ethical Hacking course in Velachery
Ethical Hacking course in T Nagar

Aparna said...

I would like to thank you so much for sharing with us and I have many ideas after visiting your post. Well done...
JMeter Training in Chennai
JMeter Certification
Linux Training in Chennai
Pega Training in Chennai
Primavera Training in Chennai
Unix Training in Chennai
Placement in Chennai
Tableau Training in Chennai
Oracle Training in Chennai
JMeter Training in T Nagar
JMeter Training in OMR

Adhuntt said...

Great blog thanks for sharing Looking for the best creative agency to fuel new brand ideas? Adhuntt Media is not just a digital marketing company in chennai. We specialize in revamping your brand identity to drive in best traffic that converts.

Karuna said...

Nice blog thanks for sharing Is this a special day for you? Beautiful and fragrant flowers are sure to make it even more amazing of a day no doubt. This is why Karuna Nursery Gardens offers you the best rental plants in Chennai that too at drop dead prices.

Unknown said...

Excellent blog thanks for sharing Pixies Beauty Shop is unlike any of the other cosmetic shops in Chennai. With tons of exclusive imported brands to choose from and the best value, this is the best shopping destination for your personal and salon needs.

Unknown said...

Awesome blog thanks for sharing While choosing your perfect ride for driving, Accord Cars comes with and the best packages for you to pick from. Self drive Cars in Chennai are done the easier. Just pick out your plan from hourly, daily, weekly and even monthly plans available.

Unknown said...

Very useful blog thanks for sharing At Pearl’s - The best Bridal Makeup Parlour in Chennai, we take personal responsibility in making sure that you look as flawless and beautiful and the marriage that you have been dreaming of. With around 16,000 successful brides in our books, you can be confident that we know our art intimately and deep.

Durai Moorthy said...

Thank you for sharing such a piece of great information very useful to us
AWS Training in Bangalore
RPA Training in Kalyan Nagar
Data Science with Python Training Bangalore
AWS Training in Kalyan Nagar
RPA training in bellandur
AWS Training in bellandur
Marathahalli AWS Training Institues
Kalyan nagar AWS training in institutes
Data Science Training in bellandur
Data Science Training in Kalyan Nagar

jagedheesh kumar said...

Very good information provided, Thanks a lot for sharing such useful information.
salesforce Training in Bangalore
uipath Training in Bangalore
blueprism Training in Bangalore

nisha raj said...

Your post is just outstanding! thanx for such a post,its really going great and great work.
python training in kalyan nagar|python training in marathahalli
selenium training in marathahalli|selenium training in bangalore
devops training in kalyan nagar|devops training in bellandur
phthon training in bangalore