Get one list of Task return objects

Using the Task Async/Await pattern for grabbing data can be a real performance enhancement. When you thread off the calls, it’s pretty normal to want Task return objects to be in one single usable collection. The example I can give is a method that needs to gather up several different categories of lookup items. These calls all return a collection of the same type.

When you await the tasks, you generally have a few options:

Await each item individually

            List<Task<List<LuItem>>> _allLus = new();
            List<LuItem> _return = new();

            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("URLTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("RVFUELTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("GENERATORFUELTYPES"));

            Task<List<LuItem>>.WaitAll(_allLus.ToArray());

            List<LuItem> _task1 = await _allLus[0];
            List<LuItem> _task2 = await _allLus[1];
            List<LuItem> _task3 = await _allLus[2];

            _return.AddRange(_task1);
            _return.AddRange(_task2);
            _return.AddRange(_task3);

            return _return;

Not sure how you feel, but this is horrible. I’m sure I’ve done something like this in the past, but I’d prefer not to think about it.

Use WhenAll to retrieve them in an Array

The Task.WhenAll, when declared with a type, will return an array of the return type. So in this case, it would return an Array of List<LuItem>. We can then do a simple LINQ query to push them all into one collection.

            List<Task<List<LuItem>>> _allLus = new();
            List<LuItem> _return = new();

            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("URLTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("RVFUELTYPES"));
            _allLus.Add(LookupsSvc.GetLuItemsByCatShortNameAsync("GENERATORFUELTYPES"));

            List<LuItem>[] _await = await Task<List<LuItem>>.WhenAll(_allLus);
            _await.ToList().ForEach(lus => _return.AddRange(lus));

            return _return;

In this example, we await the Task with WhenAll, which has a return type, as opposed to WaitAll which does not. As stated earlier, this example will return a collection as Task<List<LuItem>[]>. So we’re most of the way there. We use the ToList().ForEach LINQ query to transform the Array of Lists into a single list called _return.\

Sum of a list of values in a collection

Summing a collection that is within a collection without using nested foreach loops can be easily done with LINQ

It’s hard to think of a good name for this post. But if you have a collection and each item has a collection of values that you need to get a sum on, you can do that easily with LINQ.

Say you have a List<CartItem> in a shopping cart. Each item has a list of DecimalCost, possibly the user has ordered different sizes or colors and they each have an associated cost.

decimal _sum;
_return.CartItems.ForEach(c => _sum = c.DecimalCost.Sum());

Above we’re basically setting up an inline ForEach loop and then summing on the DecimalCost field which is actually a List<decimal>.

error NETSDK1152: Found multiple publish output files with the same relative path

I started receiving this error on the .XML file that I have included in my builds for APIs in order to enhance the Swagger descriptions. This file is enabled in the Build tab of the project properties.

This is using Visual Studio 2019 and publishing through Azure Pipelines.

Build tab on the project properties menu

This file will get created at the project root and in the bin folder when the project builds. It seems that .NET 6 build tools no longer likes having duplicate files.

Method

I have the properties of the xml documentation file in the project set to:

Build Action: None

Copy to Output Directory: Copy if newer

Then I added this to the main project file where the error is occuring in the top PropertyGroup section :

<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>

This simulates the scenario that was available up to and through .NET 5.0.

The entire block looks like

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <PackageId>YTG.AppLookups.SYS.API</PackageId>
    <Version>2.0</Version>
    <Authors>Jack Yasgar</Authors>
    <Company>Yasgar Technology Group, Inc.</Company>
    <TargetFramework>net5.0</TargetFramework>
    <RootNamespace>YTG.AppLookups.SYS.API</RootNamespace>
    <UserSecretsId>6197b17a-8e5c-4487-9574-2691abb359bb</UserSecretsId>
    <DockerDefaultTargetOS>Windows</DockerDefaultTargetOS>
    <AssemblyVersion>2.0.0.0</AssemblyVersion>
    <Configurations>Debug;Release;RV4;QA;RV4QA;PROD;RV4YTGIPROD02;RV4YTGITEST01</Configurations>
    <Platforms>AnyCPU;x64</Platforms>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
  </PropertyGroup>

Microsoft’s Notice

LINQ Safely Remove Items from a Collection

I’m sure we’ve all experienced the great idea of looping through a collection and trying to remove an item from a collection that doesn’t need to be there. You’ll get the infamous “Collection was modified; enumeration operation may not execute”. You can create a new collection and add the ones you want to that one, but that’s extra overhead.

Collection was modified; enumeration operation may not execute

This is a method that you can use that is outside of a foreach loop:

entity.TheBody.Elements.RemoveAll(a => string.IsNullOrWhiteSpace(a.Key));

In this example, I have an email named “entity” with a “TheBody” property that has a collection of Elements. The Elements have two properties, “Key” and “Value”, basically like a Dictionary entry. Creating a new list of elements means a new List<EmailElement> and then a .Clear and .AddRange, which kills more CPU cycles and milliseconds.

However, executing the above line will remove all the items from the collection that meet the criteria in the lambda.

IIS Express The specified port is in use

Port ##### is already being used by another application.

There are times when the random port selected for use by Visual Studio for IIS Express can cause an error of “The specified port is in use.” This could be because you have something installed on your device that is already using that port.

The specified port is in use. Port ##### is already being used by another application.

An error occurred launching IIS Express. Unable to launch the configured Visual Studio Development Web Server.Port ‘#####’ is in use.

In order to see if it’s really true, you can use the netsh CLI command line app. Run a command prompt and run this command:

netsh interface ipv4 show excludedportrange protocol=tcp

You’ll see a report such as this:

If the port you have setup in your launchSettings.json conflicts with one on this report, then change the value in your launch settings to a value no in this list and try again.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:5066",
      "sslPort": 0
    }
  }
}

In the above example launchSettings.json, change the applicationUrl setting to have a port that is not in use, such as http://localhost:8081 etc. If you’re going to use higher ports, remember to do a search on the web, as some ports are reserved for specials purposes. We know many of them that you should avoid, even if they don’t show in the above list:

  • 21 – FTP
  • 22 – SSH
  • 80 – Default Website
  • 443 – Secure Website
  • etc.

Basically, avoid any ports with 3 numbers or less to be safe. As you can see above, I’ve found that ports in the 5000-5999 have the least potential for conflicts with other applications and network functions.

error MSB3644: The reference assemblies for .NETFramework,Version=v4.5 were not found

error MSB3644 – An error on Azure Pipelines if your .NET Framework in your project is too old for Visual Studio 2022.

Microsoft .NET Framework

I hadn’t run a pipeline for a database project for several months since I was working on other projects. I received the failure message: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(1220,5): error MSB3644: The reference assemblies for .NETFramework,Version=v4.5 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks.

I was suspicious when I saw the reference to \2022\ in the path which turned out to be the cause of the issue. Azure pipelines were upgraded to use Visual Studio 2022 build scenarios.

I checked my SQL Project and found that it was set to .NET Framework 4.5. This is too old for Visual Studio 2022. I updated it to .NET Framework 4.7.2 and recompiled to make sure it didn’t cause any issues.

Visual Studio 2019 Target Framework Selection in Project Properties

I checked it in and merged. The job ran succuessfully.

Azure Pipelines Successful Build Notice