This is part of the NServiceBus Upgrade Guide from Version 5 to 6, which also includes the following individual upgrade guides for specific components:
Feature Details
- Assembly Scanning Changes in NServiceBus Version 6
- No Async Suffix
- Dependency Injection Changes in NServiceBus Version 6
- Deprecated TransportMessage in NServiceBus Version 6
- Endpoint API changes in NServiceBus Version 6
- Extension Seam Changes in NServiceBus Version 6
- Migrate handlers and sagas to Version 6
- Header API changes in NServiceBus Version 6
- Messaging Changes in NServiceBus Version 6
- Moving away from IBus in Version 6
- Recoverability Changes in Version 6
- Serialization Changes in NServiceBus Version 6
- Subscription Changes in NServiceBus Version 6
- Transaction Configuration Changes in NServiceBus Version 6
Transports
- Azure Service Bus Transport (Legacy) Upgrade Version 6 to 7
- RabbitMQ Transport Upgrade Version 3 to 4
- SQL Server Transport Upgrade Version 2 to 3
- SQL Server Transport Upgrade - Supporting Unicode in Headers
Persistence
- Upgrade from NServiceBus Azure Version 6
- NHibernate Persistence Upgrade Version 6 to 7
- NHibernate Persistence - Resolving incorrect timeout table indexes
- RavenDB Persistence Upgrade from 3 to 4
Hosting
Other
- Moving to the DataBus AzureBlobStorage Package
- Azure Cloud Services Host Upgrade Version 6 to 7
- NServiceBus.Azure package deprecated
- Gateway Upgrade Version 1 to 2
- NServiceBus Testing Upgrade Version 5 to 6
- Callback Changes in NServiceBus Version 6
- Migrating the distributor to use sender-side distribution
- Tool and Helper Changes in NServiceBus Version 6
Starting with NServiceBus version 6, all APIs that contain potentially I/O-bound code are asynchronous. Some examples include:
- Endpoint messaging methods such as Send and Publish
- Sagas and message handlers
- Message pipeline extension points
- Endpoint Start and Stop.
- Message mutators.
None of the above APIs have an Async suffix as recommended by the Microsoft convention, which states:
The name of an async method, by convention, ends with an Async suffix.
The decision not to adopt the Async suffix in NServiceBus API is intentional for several reasons.
Reason for No Async Suffix
No requirement for conflicting overloads
The Async suffix convention was adopted by necessity in .NET CLR since async APIs were added in a non-breaking version. Since C# cannot have overloads that differ only by return type, the new async APIs needed to have a different name, hence the Async suffix was used.
Adding async to NServiceBus version 6 in itself is a breaking change. In comparison to the .NET CLR APIs, NServiceBus has no requirement to support both synchronous and asynchronous versions of the API. Therefore the need to add the Async suffix does not apply.
Noise in API usage
There is already non-trivial verbosity that is added to a codebase when asynchronous principles are adopted. For example .
additions, async
and await
keywords, and Task
return values.
NServiceBus APIs do not follow Hungarian notation
No other NServiceBus APIs follow Hungarian notation. For example:
- Methods are not suffixed with the name of the type they return.
- Classes are not suffixed with "Instance" or "Static".
- Members are not suffixed access modifier names such as "Protected" or "Public".
All these things can be inferred by the IDE (e.g. Visual Studio) and the compiler, and appropriate IntelliSense and compiler messages are provided to the developer.
Therefore, in deciding on the adoption of the Async suffix it was necessary to choose between consistency with certain external .NET APIs or naming consistency within NServiceBus.
Related article: Hungarian notation disadvantages.
Async APIs should be identifiable in code
One of the arguments for the Async suffix is that all asynchronous methods should be clearly identifiable in code so as to prevent misuse of that API. However, the compiler is very efficient at identifying incorrect async
keyword usage and providing appropriate feedback to the developer. Some possible misuses are listed below with the associated compiler information.
Missing return task
When a Task method calls an async method but neglects to await that method.
public static Task TaskMethodMissingAwait()
{
var writer = new StreamWriter("stub");
writer.WriteLineAsync();
}
Results in Compiler Error CS0161
Async method with missing await
When an async Task method calls an async method but neglects to await that method.
public static async Task AsyncMethodMissingAwait()
{
var writer = new StreamWriter("stub");
writer.WriteLineAsync();
}
Results in Compiler Warning CS4014
Missing a single await
When an async Task method awaits one async method but neglects to await another.
public static async Task AsyncMethodMissingOneAwait()
{
var writer = new StreamWriter("stub");
writer.WriteLineAsync();
await writer.WriteLineAsync();
}
Results in Compiler Warning CS4014
Treat warnings as errors
Note that several of the above examples contain warnings rather than errors. As such, it is necessary to either treat all warnings as errors or nominate specific warnings to be treated as errors via Errors and Warnings.
Cases not detected by the compiler
There are some cases that are not detected by the compiler. For example:
public static Task MissingTaskUsage1()
{
var writer = new StreamReader("stub");
// Note the returned instance is not used
writer.ReadLineAsync();
return writer.ReadLineAsync();
}
public static void MissingTaskUsage2()
{
var writer = new StreamReader("stub");
// Note the returned instance is not used
writer.ReadLineAsync();
}
In these scenarios, there are other possible solutions, such as writing a Roslyn analyzer.
Async not necessary when reading code
The above examples show how difficult it is to incorrectly use async APIs. As such, async API usage is clearly identifiable in code by the associated await
usage that is required.
Other libraries with no Async suffix.
Other libraries are also taking the same approach. For example: