Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mrdrbob committed Apr 28, 2021
0 parents commit a977973
Show file tree
Hide file tree
Showing 18 changed files with 565 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.
18 changes: 18 additions & 0 deletions .github/ISSUE_TEMPLATE/documentation_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
name: Documentation Request
about: Create a report to help us improve our documentation
title: ''
labels: 'Documentation-Request'
assignees: ''

---


**Section Needing extra clarity**
Which feature section

**Extra details**
Enter here the section that could be improved, or clarified.

**Other notes**

20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
36 changes: 36 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Update NuGet

on: [push]

jobs:
build:
runs-on: windows-latest

name: Publish Nuget Package
steps:
- uses: actions/checkout@master
- name: Setup .NET Core 2.1
uses: actions/setup-dotnet@v1
with:
dotnet-version: 2.1.x
- name: Setup .NET Core 3.1
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.x
- name: Setup .NET Core 5.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Run Tests
run:
dotnet test "./tests/Blend.ActionQueue.Tests/Blend.ActionQueue.Tests.csproj"
- name: Build Package
run:
dotnet build -c Release "./src/Blend.ActionQueue/Blend.ActionQueue.csproj"
- name: Package Release
run:
dotnet pack -c Release --no-build -o out "./src/Blend.ActionQueue/Blend.ActionQueue.csproj"
- name: Publish Nuget to GitHub registry
run: ls .\out\*.nupkg | foreach { dotnet nuget push $_ -s https://nuget.pkg.github.com/blendinteractive/index.json -k $env:GITHUB_TOKEN }
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bin/
obj/
*.user
*.suo
/.vs/

packages/
31 changes: 31 additions & 0 deletions Blend.ActionQueue.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blend.ActionQueue", "src\Blend.ActionQueue\Blend.ActionQueue.csproj", "{F8A723BE-F1BA-4235-AAC7-2FA98B002792}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blend.ActionQueue.Tests", "tests\Blend.ActionQueue.Tests\Blend.ActionQueue.Tests.csproj", "{87835CBE-2C94-4D91-A7F3-8881FB0E730A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F8A723BE-F1BA-4235-AAC7-2FA98B002792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8A723BE-F1BA-4235-AAC7-2FA98B002792}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8A723BE-F1BA-4235-AAC7-2FA98B002792}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8A723BE-F1BA-4235-AAC7-2FA98B002792}.Release|Any CPU.Build.0 = Release|Any CPU
{87835CBE-2C94-4D91-A7F3-8881FB0E730A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87835CBE-2C94-4D91-A7F3-8881FB0E730A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87835CBE-2C94-4D91-A7F3-8881FB0E730A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87835CBE-2C94-4D91-A7F3-8881FB0E730A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3233833A-D88F-40DA-A89C-EBCEEE3F5C2F}
EndGlobalSection
EndGlobal
11 changes: 11 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.0.0] - 2021-04-28
### Added
- Abstract Action Queue
3 changes: 3 additions & 0 deletions Contributors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Contributors

* [Blend Interactive](https://github.com/BlendInteractive)
24 changes: 24 additions & 0 deletions License
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Copyright (c) 2019, Blend Interactive
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Blend Interactive nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL BLEND INTERACTIVE BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Blend.ActionQueue

This is a simple, non-durable queue for when you need something like a message queue, but with minimal setup, for small, low-volume messages or actions where persistence is not a requirement.

For example, you might use this to invalidate caches across servers without holding up the main UI thread while API calls are executed. Or you might send non-critical notifications via this queue, again freeing up the primary UI thread.

## Usage

To create a queue, implement `AbstractActionQueue<T>`, where `T` is the type of message you'll be queuing. *Note*: each instance of your queue will be a separate queue and thread, so you may want to ensure your queue is a singleton.

## Example Implementation

```csharp
/// <summary>
/// This is an example message type where the `Action` to execute is provided in the constructor.
/// </summary>
public class ExampleAction
{
private readonly Action action;

public ExampleAction(Action action)
{
this.action = action;
}

public void Execute() => action?.Invoke();
}

/// <summary>
/// This is an example queue which accepts `ExampleAction` as its "message", and executes the Action.
/// To log errors, you would set `OnError` to some kind of error handler `Action`.
/// </summary>
public class ExampleQueue : AbstractActionQueue<ExampleAction>
{
public ExampleQueue(System.Threading.CancellationToken token) : base(token)
{
}

public Action<Exception> OnError { get; set; }

protected override void LogException(Exception ex) => OnError?.Invoke(ex);

protected override void ProcessItem(ExampleAction item) => item.Execute();
}
```

## Example Usage

```csharp
var exampleQueue = new ExampleQueue(CancellationToken.None);

int totalExecutions = 0;

exampleQueue.QueueAction(new ExampleAction(() => totalExecutions += 2));
exampleQueue.QueueAction(new ExampleAction(() => totalExecutions += 4));
exampleQueue.QueueAction(new ExampleAction(() => totalExecutions += 8));

// Hopefully adding 3 numbers doesn't take longer than 100ms
Thread.Sleep(100);
Assert.Equal(14, totalExecutions);
```

## Handling Errors

Because `ProcessItem` is being called on a background thread, any errors thrown in the `ProcessItem` will be caught and `LogException` will be called with the exception to pass it through to whatever logging you're using.

## Caveats

The queue is backed by a `BlockingCollection<T>` and items are popped off and executed one at a time. In theory, the queue itself should not have any race conditions or other threading issues, but... mutlithreading is hard.

Keep in mind if using this in an ASP.NET context, you will not be able to rely on things like `HttpContext.Current`, as this is running in a separate thread.

Each instance of a queue is a separate thread and queue. You'll most likely want each queue type to be a singleton. For example:

```csharp
// WRONG
new ExampleQueue(CancellationToken.None).QueueAction(new ExampleAction(() => Console.WriteLine("No.")));
new ExampleQueue(CancellationToken.None).QueueAction(new ExampleAction(() => Console.WriteLine("Don't do this.")));
new ExampleQueue(CancellationToken.None).QueueAction(new ExampleAction(() => Console.WriteLine("It's wrong.")));

// OK
private static readonly ExampleQueue queue = new new ExampleQueue(CancellationToken.None);

queue.QueueAction(new ExampleAction(() => Console.WriteLine("OK.")));
queue.QueueAction(new ExampleAction(() => Console.WriteLine("Do this.")));
queue.QueueAction(new ExampleAction(() => Console.WriteLine("It's fine.")));
```

This queue is not durable. If your application shuts down or restarts with items pending in the queue, those items will be lost.
57 changes: 57 additions & 0 deletions src/Blend.ActionQueue/AbstractActionQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Concurrent;
using System.Threading;

namespace Blend.ActionQueue
{
public abstract class AbstractActionQueue<T>
{
private readonly BlockingCollection<T> Queue = new BlockingCollection<T>();
private Thread queueThread;
private CancellationToken token;

protected abstract void LogException(Exception ex);
protected abstract void ProcessItem(T item);

public AbstractActionQueue(CancellationToken token)
{
this.token = token;
}

void StartQueueIfNotStarted()
{
if (queueThread == null || !queueThread.IsAlive)
{
queueThread = new Thread(QueueLoop);

queueThread.Start();
}
}

void QueueLoop()
{
while (!token.IsCancellationRequested)
{
try
{
var actions = Queue.GetConsumingEnumerable(token);
foreach (var action in actions)
{
ProcessItem(action);
}
}
catch (Exception ex)
{
LogException(ex);
}
}
}

public T QueueAction(T action)
{
StartQueueIfNotStarted();
Queue.Add(action);
return action;
}
}
}
16 changes: 16 additions & 0 deletions src/Blend.ActionQueue/Blend.ActionQueue.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>A simple, non-durable queue for when you need something like a message queue, but with minimal setup, for small, low-volume messages or actions where persistence is not a requirement.</Description>
<Version>1.0.0</Version>
<PackageReleaseNotes>Initial Release</PackageReleaseNotes>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Authors>Blend Interactive</Authors>
<PackageProjectUrl>https://github.com/blendinteractive/Blend.ActionQueue</PackageProjectUrl>
<RepositoryUrl>https://github.com/blendinteractive/Blend.ActionQueue.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>

</Project>
Loading

0 comments on commit a977973

Please sign in to comment.