Scott Hanselman

ASP.NET Core Diagnostic Scenarios

September 30, 2021 Comment on this post [2] Posted in ASP.NET | DotNetCore | Open Source
Sponsored By

ThreadingDavid and friends has a great repository filled with examples of "broken patterns" in ASP.NET Core applications. It's a fantastic learning resource with both markdown and code that covers a number of common areas when writing scalable services in ASP.NET Core. Some of the guidance is general purpose but is explained through the lens of writing web services.

Here's a few great DON'T and DO examples, but be sure to Star the repo and check it out for yourself! This is somewhat advanced stuff but if you are doing high output low latency web services AT SCALE these tips will make a huge difference when you're doing a something a hundred thousand time a second!

DON'T - This example uses the legacy WebClient to make a synchronous HTTP request.

public string DoSomethingAsync()
{
var client = new WebClient();
return client.DownloadString(http://www.google.com);
}

DO - This example uses an HttpClient to asynchronously make an HTTP request.

static readonly HttpClient client = new HttpClient();

public Task<string> DoSomethingAsync()
{
return client.GetStringAsync("http://www.google.com");
}

Here's a list of ASP.NET Core Guidance. This one is fascinating. ASP.NET Core doesn't buffer responses which allows it to be VERY scalable. Massively so. As such you do need to be aware that things need to happen in a certain order - Headers come before Body, etc so you want to avoid adding headers after the HttpResponse has started.

DON'T - Add headers once you've started sending the body.

app.Use(async (next, context) =>
{
await context.Response.WriteAsync("Hello ");

await next();

// This may fail if next() already wrote to the response
context.Response.Headers["test"] = "value";
});

DO - Either check if it's started before you send the headers:

app.Use(async (next, context) =>
{
await context.Response.WriteAsync("Hello ");

await next();

// Check if the response has already started before adding header and writing
if (!context.Response.HasStarted)
{
context.Response.Headers["test"] = "value";
}
});

Or even BETTER, add the headers on the OnStarting call back to guarantee they are getting set.

app.Use(async (next, context) =>
{
// Wire up the callback that will fire just before the response headers are sent to the client.
context.Response.OnStarting(() =>
{
context.Response.Headers["test"] = "value";
return Task.CompletedTask;
});

await next();
});

There's a ton of great guidance around async programming. If you are returning something small or trivial, like a simple value, DON'T Task<>:

public class MyLibrary
{
public Task<int> AddAsync(int a, int b)
{
return Task.Run(() => a + b);
}
}

DO use ValueTask<> as this example not only doesn't use an extra threads and avoids heap allocation entirely:

public class MyLibrary
{
public ValueTask<int> AddAsync(int a, int b)
{
return new ValueTask<int>(a + b);
}
}

There's a ton of good learning over there so go check it out! https://github.com/davidfowl/AspNetCoreDiagnosticScenarios


Sponsor: Make login Auth0’s problem. Not yours. Provide the convenient login features your customers want, like social login, multi-factor authentication, single sign-on, passwordless, and more. Get started for free.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Differences between Hashtable vs Dictonary vs ConcurrentDictionary vs ImmutableDictionary

September 28, 2021 Comment on this post [9] Posted in DotNetCore | Musings
Sponsored By

DictionariesI'm very much enjoying David Fowler's tweets, and since he doesn't have a blog, I will continue to share and expand on his wisdom so that it might reach a larger audience.

He had a conversation with Stephen Toub where Stephen points out that ".NET has 4 built-in dictionary/map types [and] there’s no guidance on when to use what, mostly individual documentation on each implementation."

  • Hashtable
  • Dictionary
  • ConcurrentDictionary
  • ImmutableDictionary

There is actually some good documentation on C# Collections and Data Structures here that we can compare and combine with Stephen Toub's good advice (via David) as well!

There are two main types of collections; generic collections and non-generic collections. Generic collections are type-safe at compile time. Because of this, generic collections typically offer better performance.

Definitely important to remember. Generics have been around since .NET Framework 2.0 around 15 years ago so this is a good reason to consider avoiding Hashtable and using Dictionary<> instead. Hashtable is weakly typed and while it allows you to have keys that map to different kinds of objects which may seem attractive at first, you'll need to "box" the objects up and boxing and unboxing is expensive. You'll almost always want to use Dictionary instead.

If you're accessing your collection across threads, consider the System.Collections.Concurrent namespace or using System.Collections.Immutable which is thread-safe because you'll always be working on a copy as the original collection is immutable (not modifiable).

David says this about

  • ConcurrentDictionary - "Good read speed even in the face of concurrency, but it’s a heavyweight object to create and slower to update."

Or perhaps

  • Dictionary with lock - "Poor read speed, lightweight to create and medium update speed."
  • Dictionary as immutable object - "best read speed and lightweight to create but heavy update. Copy and modify on mutation e.g. new Dictionary(old).Add(key, value)"
  • Hashtable - "Good read speed (no lock required), same-ish weight as dictionary but more expensive to mutate and no generics!"
  • ImmutableDictionary - "Poorish read speed, no locking required but more allocations require to update than a dictionary."

Another one that isn't often used but I'll add as it's good to know about is

  • KeyedCollection - Generic and ordered. Uses Dictionary and List underneath

This is great advice from David:

Use the most obvious one until it bites you. Most software engineering is like this.

Measure and test, measure and test. Good luck to you!


Sponsor: Make login Auth0’s problem. Not yours. Provide the convenient login features your customers want, like social login, multi-factor authentication, single sign-on, passwordless, and more. Get started for free.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

How to make Shared Google Calendars show up on your iPhone and iPad Calendar

September 23, 2021 Comment on this post [0] Posted in Musings
Sponsored By

My niece just started her MBA at a local university and that uni is a G Suite/Google/Gmail user. Her professors share their class calendars (vs inviting the students to events) so everything is a "Shared with You" shared calendar. That means the events aren't on your primary Google Calendar, they are read-only shares to you.

My niece uses an iPhone and wanted to calendars to sync with her iPhone calendar she already uses. Google help and everyone else says "install Google Calendar." Sure, that works and she can see the calendars in that other apps, but again, it's totally not integrated with her life and existing Calendar App on iPhone, Mac, and iPad.

Turns out there is a 12 year old page deep in Google Calendar at https://www.google.com/calendar/syncselect that you can visit to "reshare" those shared calendars to external users like iOS. Love that Copyright 2009 action and the ongoing dedication to improvement in Google Suite and Calendar for the real features that people need /s.

Google Calendar Sync Select

Make sure you are signed into the right Google Account before you click that link. At this point, return to your iPhone/iPad Calendar app and tap Calendars at the bottom. Check the ones you want to see, and press done. Wait a few minutes and your Google Shared Calendars will start to sync to your iOS Calendar!

Hope this helps.


Sponsor: YugabyteDB is a distributed SQL database designed for resilience and scale. It is 100% open source, PostgreSQL-compatible, enterprise-grade, and runs across all clouds. Sign up and get a free t-shirt.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

The code worked differently when the moon was full

September 21, 2021 Comment on this post [7] Posted in Bugs | Musings
Sponsored By

Full Moon

I love a good bug, especially ones that are initially hard to explain but then turn into forehead slapping moments - of course!

There's a bug over on Github called Hysteresis effect on threadpool hill-climbing that is a super interesting read.

Hill climbing is an algorithmic technique when you have a problem (a hill) and then you improve and improve (climb) until you have reached some maximum acceptable solution (reach the top).

Sébastien, the OP of the bug, says there's a "Hysteresis effect" on the threadpool. "Hysteresis is the dependence of the state of a system on its history." Something weird is happening now because something happened before...but what was it?

Sawtooth up and down graphs aren't THAT interesting...but look at the x-axis. This isn't showing minute by minute or even millisecond by millisecond ups and downs like you may have seen before. This x-axis uses months as its unit of measure. Read that again and drink it in.

A sawtooth graph going up and down month to month

Things are cool in February until they are bad for weeks and then cool in March and it moves along a cycle. Not strictly the cycle of the moon but close.

He's seeing the number of cores being used changing from month to month when using PortableThreadPool

We have noticed a periodic pattern on the threadpool hill-climbing logic, which uses either n-cores or n-cores + 20 with an hysteresis effect that switches every 3-4 weeks.

Did you know (I know because I'm old) that Windows 95, for a time, was unable to run longer than 49.7 days of runtime? If you ran it that long it would eventually crash! That's because there's 86M ms in a day, i.e. 1000 * 60 * 60 * 24 = 86,400,000 and 32 bits is 4,294,967,296 so 4,294,967,296 / 86,400,000 = 49.7102696 days!

Kevin in the Github issues notes this as well:

The whole period of square wave sounds an awful lot like it is around 49.7 days. That is how long it takes GetTickCount() to wrap around. On POSIX platforms the platform abstraction layer implements this, and the value returned for that is based not on uptime but on wall clock time, which matches with all machines changing on the same day.

This 49.7 days number is well known as it's how long it take before GetTickCount() overflows/wraps. Kevin goes on to actually give the changeover dates which correspond to the graph!

  • Thu Jan 14 2021
  • Sun Feb 07 2021
  • Thu Mar 04 2021
  • Mon Mar 29 2021
  • Fri Apr 23 2021

He then finds the code in PortableThreadPool.cs that explains the issue:

private bool ShouldAdjustMaxWorkersActive(int currentTimeMs) 
{
// We need to subtract by prior time because Environment.TickCount can wrap around, making a comparison of absolute times unreliable.
int priorTime = Volatile.Read(ref _separated.priorCompletedWorkRequestsTime);
int requiredInterval = _separated.nextCompletedWorkRequestsTime - priorTime;
int elapsedInterval = currentTimeMs - priorTime;
if (elapsedInterval >= requiredInterval)
{
...

He says, and this is all Kevin:

currentTimeMs is Environment.TickCount, which in this case happens to be negative.

The if clause controls if the hill climbing is even run.

_separated.priorCompletedWorkRequestsTime and _separated.nextCompletedWorkRequestsTime start out as zero on process start, and only get updated if the hill climbing code is run.

Therefore, requiredInterval = 0 - 0 and elapsedInterval = negativeNumber - 0. This causes the if statement to become
if (negativeNumber - 0 >= 0 - 0) which returns false, so the hill climbing code is not run, and therefore the variables never get updated, and remain zero. The native version of the thread pool code does all math with unsigned numbers which would avoid such a bug, and it's equivalent part is not even quite the same math in the first place.

The easy fix here is probably to use unsigned arithmetic, but alternatively having the two fields get initialized to Environment.TickCount probably also work

Back to me. Fabulous. The fix is to cast the results to unsigned ints via (uint).

Before:

int requiredInterval = _separated.nextCompletedWorkRequestsTime - priorTime;
int elapsedInterval = currentTimeMs - priorTime;

After:

uint requiredInterval = (uint)(_separated.nextCompletedWorkRequestsTime - priorTime);
uint elapsedInterval = (uint)(currentTimeMs - priorTime);

What an interesting and insidious bug! Bugs based on a time calculations can often show themselves later when view through a longer lens and scope of time...sometimes WAY longer than you'd expect.


Sponsor: YugabyteDB is a distributed SQL database designed for resilience and scale. It is 100% open source, PostgreSQL-compatible, enterprise-grade, and runs across all clouds. Sign up and get a free t-shirt.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Implicit Usings in .NET 6

September 16, 2021 Comment on this post [2] Posted in DotNetCore
Sponsored By

Magic".NET 6 introduces implicit namespace support for C# projects. To reduce the amount of using directives boilerplate in .NET C# project templates, namespaces are implicitly included by utilizing the global using feature introduced in C# 10."

NOTE: Did you know that Visual Basic has had this very feature forever?

Remember that C# as a language is itself versioned and in .NET 6 we'll have support for C# 10 features like global usings, which are super cool.

Since we don't want to break existing stuff, there's some things to consider. First, for new projects this is on by default but for existing projects this will be off by default. This offers the best of both worlds.

When you create a new .NET 6 project it will enable this new property:

<ImplicitUsings>enable</ImplicitUsings>

Read more about this breaking change here. This build property builds upon (utilizes) the C# global using feature feature which means any .cs in your project can have a line like:

global using global::SomeNamespace;

The SDK uses a target to autogenerate a .cs file called ImplicitNamespaceImports.cs that will be in your obj folder, but you can - if you desire - have full control and add or remove namespaces to taste.

This gives advanced users who understand target file a huge amount control while still allowing newbies to reap the benefits. Other way to think about it is - if you care, you can control it all. If you don't, it'll just make things easier and cleaner.

Let's look at some code to point out that it's pretty cool. Oleg gives a great example doing some basic threading where there's three lines of code (cool) and three more lines of usings to bring in the namespace support for the actual work (less cool).

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

Console.WriteLine("Hello World");
await Task.Delay(1000);
List<int> _ = new ();

With implicating usings (implicitly bringing in default namespaces) .NET apps with C# 10 can do more out of the box. It's faster to get started because the 90% of the stuff you do all the time is already available and ready to be used!

Maybe this example is too simple? What If you were using a simple Web Worker app? Check out Wade's example.

System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading
System.Threading.Tasks
System.Net.Http.Json
Microsoft.AspNetCore.Builder
Microsoft.AspNetCore.Hosting
Microsoft.AspNetCore.Http
Microsoft.AspNetCore.Routing
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging

This is a lot of boilerplate if you just want a web app. If I'm using the Microsoft.Net.Sdk.Worker SDK in my project file, or just Microsoft.NET.Sdk.Web, I don't have think about or include any of these - they are there implicitly!

You may initially love implicit usings, as I do, or you may find it to be too "magical." I would remind you that most innovations feel magical, especially if they aren't in your face. The Garbage Collector is taken for granted by the majority of .NET developers, while I found it magical when I had spent the previous 10 years managing my own memory down to the byte.

Hope you enjoy this new feature as we get closer to .NET 6's release.


Sponsor:  The No. 1 reason developers choose Couchbase? You can use your existing SQL++ skills to easily query and access JSON. That’s more power and flexibility with less training. Learn more.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.