SerialQueue.NetStandard
2.1.2412.12
dotnet add package SerialQueue.NetStandard --version 2.1.2412.12
NuGet\Install-Package SerialQueue.NetStandard -Version 2.1.2412.12
<PackageReference Include="SerialQueue.NetStandard" Version="2.1.2412.12" />
paket add SerialQueue.NetStandard --version 2.1.2412.12
#r "nuget: SerialQueue.NetStandard, 2.1.2412.12"
// Install SerialQueue.NetStandard as a Cake Addin #addin nuget:?package=SerialQueue.NetStandard&version=2.1.2412.12 // Install SerialQueue.NetStandard as a Cake Tool #tool nuget:?package=SerialQueue.NetStandard&version=2.1.2412.12
SerialQueue
C# Implementation of a SerialQueue in the style of Apple's Grand Central Dispatch queues.
Provided as a Nuget package built for NetStandard20, Net462 and .NET 6.0 and .NET 8.0
This is a fork of borland/SerialQueue
What is a Serial Queue?
Apple's Grand Central Dispatch library provides three kinds of "operation queues".
An operation queue is a lightweight abstraction over a thread, and can be used for concurrency and thread safety
Concurrent, "Main" and Serial.
Neither Concurrent nor Main queues are neccessary in .NET as a concurrent queue is basically the same as Task.Run
or ThreadPool.QueueUserWorkItem
, and the Main queue is basically the same as Dispatcher.BeginInvoke
(for WPF) or whatever the equivalent for your particular UI framework happens to be.
Serial queues on the other hand have no built-in equivalent. Apple's documentation describes them:
Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue. The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue. Serial queues are often used to synchronize access to a specific resource. You can create as many serial queues as you need, and each queue operates concurrently with respect to all other queues. In other words, if you create four serial queues, each queue executes only one task at a time but up to four tasks could still execute concurrently, one from each queue.
Why would I want to use one?
There are quite a few scenarios where one might like to use a thread to ensure thread-safety of some object (e.g. "All accesses to the private data of X object must be performed on thread Y"), however threads themselves are often too resource-intensive to be able to do that at a smaller scale (e.g. where you have thousands of such objects.)
To get around this in the past I have seen (and used) things like thread sharing, or fallen back to "full blown" concurrency where things just run randomly on a threadpool and locking must be used everywhere. Both of these options are more complex and dangerous, particularly full blown concurrency where all methods must all lock correctly.
Serial queues offer a very interesting option in the "middle" of these two spaces.
- They are very lightweight (each queue is literally not much more than a List and a few lock objects), so you can easily have thousands of them
- They have a usage model which is similar to threads and is easier to reason about
Example
var q = new SerialQueue();
q.DispatchAsync(() => {
Console.WriteLine("a");
});
q.DispatchAsync(() => {
Console.WriteLine("b");
});
In the above example, both operations are guaranteed to execute in-order and guaranteed not to execute at the same time. Thus, the actions are thread-safe and easy to reason about.
The actual execution of the functions is managed by the built-in .NET ThreadPool (the default implementation just uses Task.Run
) so many thousands of queues will be backed by perhaps 8 or so underlying OS threads in the threadpool
Enhancements for .NET
This serial queue supports async/await - i.e. if you are running within the context of a serial queue, then it will be captured across Async/Await
var q = new SerialQueue();
q.DispatchAsync(async () => {
// we are on the queue
var response = await SomeNetworkRequest();
// we are still on the queue, it was captured by the await
});
You can also await the queue itself directly in order to "jump" to it if you are in an existing async method
SerialQueue m_queue = new SerialQueue();
// imagine this is a WPF or winforms app
public void Button_Click()
{
// here we are on the UI main thread
var result = await DoBackgroundProcessing();
// and we are still on the UI thread because 'await DoBackgroundProcessing' captured the sync context.
MyTextBox.Text = result;
}
private async Task<string> DoBackgroundProcessing()
{
// at this point we are still on the UI main thread
await m_queue;
// now we are OFF the main UI thread and onto the serial queue (behind the scenes we're on a threadpool thread)
var response = await SendNetworkRequest();
// still on the serial queue
return response;
}
More Documentation is available on the Github wiki
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.6.2
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.1.2412.12 | 89 | 12/1/2024 |
2.1.2406.61 | 121 | 6/6/2024 |
1.0.0.2 | 4,095 | 3/8/2017 |
1.0.0.1 | 1,057 | 3/8/2017 |