├── .gitignore
├── Assets
├── logo.ico
└── logo.png
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DONATIONS.md
├── LICENSE.md
├── LOGO.md
├── README.md
└── src
├── TaskHandler.sln
├── TaskHandler
├── TaskDetails.cs
├── TaskHandler.csproj
├── TaskHandler.xml
├── TaskQueue.cs
├── TaskRunWithTimeout.cs
└── logo.ico
├── Test.RunWithTimeout
├── Program.cs
└── Test.RunWithTimeout.csproj
├── Test.RunWithTimeoutHttp
├── Program.cs
└── Test.RunWithTimeoutHttp.csproj
└── Test
├── Program.cs
└── Test.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 | *.lock
10 | *.snk
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # DNX
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 |
50 | *_i.c
51 | *_p.c
52 | *_i.h
53 | *.ilk
54 | *.meta
55 | *.obj
56 | *.pch
57 | *.pdb
58 | *.pgc
59 | *.pgd
60 | *.rsp
61 | *.sbr
62 | *.tlb
63 | *.tli
64 | *.tlh
65 | *.tmp
66 | *.tmp_proj
67 | *.log
68 | *.vspscc
69 | *.vssscc
70 | .builds
71 | *.pidb
72 | *.svclog
73 | *.scc
74 |
75 | # Chutzpah Test files
76 | _Chutzpah*
77 |
78 | # Visual C++ cache files
79 | ipch/
80 | *.aps
81 | *.ncb
82 | *.opendb
83 | *.opensdf
84 | *.sdf
85 | *.cachefile
86 | *.VC.db
87 | *.VC.VC.opendb
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 | *.sap
94 |
95 | # TFS 2012 Local Workspace
96 | $tf/
97 |
98 | # Guidance Automation Toolkit
99 | *.gpState
100 |
101 | # ReSharper is a .NET coding add-in
102 | _ReSharper*/
103 | *.[Rr]e[Ss]harper
104 | *.DotSettings.user
105 |
106 | # JustCode is a .NET coding add-in
107 | .JustCode
108 |
109 | # TeamCity is a build add-in
110 | _TeamCity*
111 |
112 | # DotCover is a Code Coverage Tool
113 | *.dotCover
114 |
115 | # Visual Studio code coverage results
116 | *.coverage
117 | *.coveragexml
118 |
119 | # NCrunch
120 | _NCrunch_*
121 | .*crunch*.local.xml
122 | nCrunchTemp_*
123 |
124 | # MightyMoose
125 | *.mm.*
126 | AutoTest.Net/
127 |
128 | # Web workbench (sass)
129 | .sass-cache/
130 |
131 | # Installshield output folder
132 | [Ee]xpress/
133 |
134 | # DocProject is a documentation generator add-in
135 | DocProject/buildhelp/
136 | DocProject/Help/*.HxT
137 | DocProject/Help/*.HxC
138 | DocProject/Help/*.hhc
139 | DocProject/Help/*.hhk
140 | DocProject/Help/*.hhp
141 | DocProject/Help/Html2
142 | DocProject/Help/html
143 |
144 | # Click-Once directory
145 | publish/
146 |
147 | # Publish Web Output
148 | *.[Pp]ublish.xml
149 | *.azurePubxml
150 | # TODO: Comment the next line if you want to checkin your web deploy settings
151 | # but database connection strings (with potential passwords) will be unencrypted
152 | *.pubxml
153 | *.publishproj
154 |
155 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
156 | # checkin your Azure Web App publish settings, but sensitive information contained
157 | # in these scripts will be unencrypted
158 | PublishScripts/
159 |
160 | # NuGet Packages
161 | *.nupkg
162 | # The packages folder can be ignored because of Package Restore
163 | **/packages/*
164 | # except build/, which is used as an MSBuild target.
165 | !**/packages/build/
166 | # Uncomment if necessary however generally it will be regenerated when needed
167 | #!**/packages/repositories.config
168 | # NuGet v3's project.json files produces more ignoreable files
169 | *.nuget.props
170 | *.nuget.targets
171 |
172 | # Microsoft Azure Build Output
173 | csx/
174 | *.build.csdef
175 |
176 | # Microsoft Azure Emulator
177 | ecf/
178 | rcf/
179 |
180 | # Windows Store app package directories and files
181 | AppPackages/
182 | BundleArtifacts/
183 | Package.StoreAssociation.xml
184 | _pkginfo.txt
185 |
186 | # Visual Studio cache files
187 | # files ending in .cache can be ignored
188 | *.[Cc]ache
189 | # but keep track of directories ending in .cache
190 | !*.[Cc]ache/
191 |
192 | # Others
193 | ClientBin/
194 | ~$*
195 | *~
196 | *.dbmdl
197 | *.dbproj.schemaview
198 | *.jfm
199 | *.publishsettings
200 | node_modules/
201 | orleans.codegen.cs
202 |
203 | # Since there are multiple workflows, uncomment next line to ignore bower_components
204 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
205 | #bower_components/
206 |
207 | # RIA/Silverlight projects
208 | Generated_Code/
209 |
210 | # Backup & report files from converting an old project file
211 | # to a newer Visual Studio version. Backup files are not needed,
212 | # because we have git ;-)
213 | _UpgradeReport_Files/
214 | Backup*/
215 | UpgradeLog*.XML
216 | UpgradeLog*.htm
217 |
218 | # SQL Server files
219 | *.mdf
220 | *.ldf
221 |
222 | # Business Intelligence projects
223 | *.rdl.data
224 | *.bim.layout
225 | *.bim_*.settings
226 |
227 | # Microsoft Fakes
228 | FakesAssemblies/
229 |
230 | # GhostDoc plugin setting file
231 | *.GhostDoc.xml
232 |
233 | # Node.js Tools for Visual Studio
234 | .ntvs_analysis.dat
235 |
236 | # Visual Studio 6 build log
237 | *.plg
238 |
239 | # Visual Studio 6 workspace options file
240 | *.opt
241 |
242 | # Visual Studio LightSwitch build output
243 | **/*.HTMLClient/GeneratedArtifacts
244 | **/*.DesktopClient/GeneratedArtifacts
245 | **/*.DesktopClient/ModelManifest.xml
246 | **/*.Server/GeneratedArtifacts
247 | **/*.Server/ModelManifest.xml
248 | _Pvt_Extensions
249 |
250 | # Paket dependency manager
251 | .paket/paket.exe
252 | paket-files/
253 |
254 | # FAKE - F# Make
255 | .fake/
256 |
257 | # JetBrains Rider
258 | .idea/
259 | *.sln.iml
260 |
261 | # CodeRush
262 | .cr/
263 |
264 | # Python Tools for Visual Studio (PTVS)
265 | __pycache__/
266 | *.pyc
267 |
268 | # Cake - Uncomment if you are using it
269 | # tools/
270 |
271 |
--------------------------------------------------------------------------------
/Assets/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/TaskHandler/37a38c8af340cec1b3a86b94b3dc032f9fa224aa/Assets/logo.ico
--------------------------------------------------------------------------------
/Assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/TaskHandler/37a38c8af340cec1b3a86b94b3dc032f9fa224aa/Assets/logo.png
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## Current Version
4 |
5 | v1.0.x
6 |
7 | - Initial release
8 |
9 | ## Previous Versions
10 |
11 | Notes from previous versions will be placed here.
12 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
44 |
45 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq
46 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing!
4 |
5 | The following is a set of guidelines for contributing to our project on Github. These are mostly guidelines, not rules.
6 |
7 | ## Code of Conduct
8 |
9 | This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to project moderators.
10 |
11 | ## Pull Requests
12 |
13 | Please follow these guidelines when submitting pull requests (PRs):
14 |
15 | - PRs should be manageable in size to make it easy for us to validate and integrate
16 | - Each PR should be contained to a single fix or a single feature
17 | - Describe the motivation for the PR
18 | - Describe a methodology to test and validate, if appropriate
19 |
20 | Please ensure that the code in your PR follows a style similar to that of the project. If you find a material discrepancy between the style followed by the project and a de facto standard style given the project language and framework, please let us know so we can amend and make our code more maintainable.
21 |
22 | ## Asking Questions
23 |
24 | Prior to asking questions, please review closed issues and wiki pages. If your question is not answered in either of those places, please feel free to file an issue! This will also help us to build out documentation.
25 |
26 | ## Reporting Bugs
27 |
28 | If you encounter an issue, please let us know! We kindly ask that you supply the following information with your bug report when you file an issue. Feel free to copy/paste from the below and use as a template.
29 |
30 | --- Bug Report ---
31 |
32 | Operating system and version: Windows 10
33 | Framework and runtime: .NET Core 2.0
34 | Issue encountered: The widget shifted left
35 | Expected behavior: The widget should have shifted right
36 | Steps to reproduce: Instantiate the widget and call .ShiftRight()
37 | Sample code encapsulating the problem:
38 | ```
39 | Widget widget = new Widget();
40 | widget.ShiftRight();
41 | ```
42 | Exception details: [insert exception output here]
43 | Stack trace: [if appropriate]
44 |
45 | --- End ---
46 |
47 | ## Suggesting Enhancements
48 |
49 | Should there be a way that you feel we could improve upon this project, please feel free to file an issue and use the template below to provide the necessary details to support the request.
50 |
51 | Some basic guidelines for suggesting enhancements:
52 |
53 | - Use a clear and descriptive title for the issue to identify the suggestion.
54 | - Provide a step-by-step description of the suggested enhancement in as many details as possible.
55 | - Provide specific examples to demonstrate the steps including copy/pasteable snippets where possible
56 | - Describe the current behavior and the behavior you would like to see
57 | - Describe the usefulness of the enhancement to yourself and potentially to others
58 |
59 | --- Enhancement Request ---
60 |
61 | Enhancement request title: Widgets should have a color attribute
62 | Use case: I want to specify what color a widget is
63 | Current behavior: Widgets don't have a color
64 | Requested behavior: Allow me to specify a widget's color
65 | Recommended implementation: Add a Color attribute to the Widget class
66 | Usefulness of the enhancement: All widgets have color, and everyone has to build their own implementation to set this
67 |
68 | --- End ---
69 |
--------------------------------------------------------------------------------
/DONATIONS.md:
--------------------------------------------------------------------------------
1 | ## Donations
2 |
3 | If you're interested in financially supporting this work on other open source projects I manage, first of all, thank you! It brings me delight to know that this software has helped you in some way. Please find below address details for donations using
4 |
5 | ### Traditional
6 |
7 | | Method | Address |
8 | |--------|---------|
9 | | PayPal | @joelchristner - https://paypal.me/joelchristner?country.x=US&locale.x=en_US |
10 | | Venmo | @Joel-Christner - https://account.venmo.com/u/Joel-Christner |
11 |
12 | ### Cryptocurrency (Mainstream)
13 |
14 | | Method | Address |
15 | |----------|---------|
16 | | Bitcoin | 3HRgnvEWQBDdWDp75CFDsz3PirCYmftDwU |
17 | | Ethereum | 0xE064dC84270e17e2Ac34b2552461d672BdBC5e36 |
18 |
19 | ### Cryptocurrency (Altcoins)
20 |
21 | | Method | Address |
22 | |--------|---------|
23 | | XRP | Tag 1765608084 Address rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg |
24 | | Shiba Inu SHIB | 0xdA58D4ba0d5823d80a0C42C69E139124B889c69a |
25 | | Algorand ALGO | FFKA23KC4BHEU5HM4OQHLEILVIYDLRXYK6WD6UI573JPUGHZR43JVHAF7A |
26 | | Stellar Lumens XML | Memo 2264929895 Address GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37 |
27 | | Dogecoin DOGE | DQ2dn4UifpYA8RyuNF1t112Y1XH5L42Rxv |
28 | | Cardano ADA | addr1vxrxyrv0phgr2xcl08dj0sfy425mhqehuu4dy99ja4nhtwqfsvgjk |
29 |
30 | If you have a coin you prefer to use, please let me know!
31 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/LOGO.md:
--------------------------------------------------------------------------------
1 | # Logo
2 |
3 | Logo file used from [Prosymbols - FlatIcon](https://www.flaticon.com/free-icons/checklist).
4 |
5 | We are thankful to the authors and designers that made this and other logos available for use!
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # TaskHandler
4 |
5 | [](https://www.nuget.org/packages/TaskHandler/) [](https://www.nuget.org/packages/TaskHandler)
6 |
7 | A simple C# class library to help manage running a queue of tasks without relinquishing control.
8 |
9 | ## New in v1.0.x
10 |
11 | - Initial release
12 |
13 | ## Feedback or Issues
14 |
15 | Encounter a bug? Think of a way this library could be even better? Please file an issue here!
16 |
17 | ## Test App
18 |
19 | Please refer to the ```Test``` project for a full example of how to exercise the class library.
20 |
21 | ## Example
22 |
23 | ```csharp
24 | using TaskHandler;
25 |
26 | TaskQueue queue = new TaskQueue(); // allow up to 32 concurrent tasks
27 | TaskQueue queue = new TaskQueue(16); // allow up to 16 concurrent tasks
28 |
29 | queue.AddTask(
30 | Guid.NewGuid(), // unique identifier
31 | "Task 1", // name for the task
32 | new Dictionary(), // any metadata you like!
33 | async (CancellationToken token) => { // your task in form of Func
34 | Console.WriteLine("Task 1 starting!");
35 | await Task.Delay(10000, token);
36 | Console.WriteLine("Task 1 ending!");
37 | });
38 |
39 | queue.AddTask(
40 | Guid.NewGuid(), // unique identifier
41 | "Task 2", // name for the task
42 | new Dictionary(), // any metadata you like!
43 | async (CancellationToken token) => { // your task in form of Func
44 | Console.WriteLine("Task 2 starting!");
45 | await Task.Delay(5000, token);
46 | Console.WriteLine("Task 2 ending!");
47 | });
48 |
49 | queue.Start();
50 | Console.WriteLine(queue.RunningCount); // Integer, the number of running tasks
51 | queue.Stop([guid]); // Cancel a specific task
52 | queue.Stop(); // Cancel all tasks
53 | ```
54 |
55 | ## For Control Freaks
56 | ```csharp
57 | queue.Logger = Console.WriteLine; // For debug messages
58 | queue.OnTaskAdded += ... // When a task is added
59 | queue.OnTaskStarted += ... // When a task starts
60 | queue.OnTaskFinished += ... // When a task finishes
61 | queue.OnTaskFaulted += ... // When a task faults
62 | queue.OnTaskCanceled += ... // When a task is canceled
63 | queue.OnProcessingStarted += ... // When the task queue is started
64 | queue.OnProcessingStopped += ... // When the task queue is stopped
65 | ```
66 |
67 | ## Run a Task with a Timeout
68 | ```csharp
69 | string result;
70 |
71 | CancellationTokenSource tokenSource = new CancellationTokenSource();
72 | CancellationToken token = tokenSource.Token;
73 |
74 | //
75 | // task without cancellation token and no return value
76 | //
77 | Func> task = async () =>
78 | {
79 | return "Hello, world!";
80 | };
81 |
82 | result = await TaskRunWithTimeout.Go(task, 2500, tokenSource); // "Hello, world!"
83 |
84 | //
85 | // task with cancellation token and return value
86 | //
87 | Func> task = async (CancellationToken token) =>
88 | {
89 | for (int i = 0; i < 25; i++) // wait 2.5 seconds in total
90 | {
91 | await Task.Delay(100);
92 | token.ThrowIfCancellationRequested(); // check for cancellation
93 | }
94 | return "Hello, world!";
95 | };
96 |
97 | result = await TaskRunWithTimeout.Go(task(token), 500, tokenSource); // throws TimeoutException
98 | result = await TaskRunWithTimeout.Go(task(token), 5000, tokenSource); // "Hello, world!"
99 |
100 | // task with cancellation token, input parameter, and return value
101 | Func> task2 = async (string text, CancellationToken token) =>
102 | {
103 | return text;
104 | };
105 |
106 | result = await TaskRunWithTimeout.Go(task2("hello world", token), 2500, tokenSource);
107 | ```
108 |
109 | ## Version History
110 |
111 | Please refer to CHANGELOG.md for version history.
112 |
--------------------------------------------------------------------------------
/src/TaskHandler.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.7.34018.315
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaskHandler", "TaskHandler\TaskHandler.csproj", "{C607651A-84B2-478F-A137-22DAF15A0F92}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{521BD444-857E-4C9E-A6C4-4467470337E4}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test.RunWithTimeout", "Test.RunWithTimeout\Test.RunWithTimeout.csproj", "{72BA2FF2-D5F9-4442-9E18-B90DC9DED11F}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.RunWithTimeoutHttp", "Test.RunWithTimeoutHttp\Test.RunWithTimeoutHttp.csproj", "{AFFA19AA-28A3-4580-BF08-A4F8EEAE5FFB}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {C607651A-84B2-478F-A137-22DAF15A0F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {C607651A-84B2-478F-A137-22DAF15A0F92}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {C607651A-84B2-478F-A137-22DAF15A0F92}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {C607651A-84B2-478F-A137-22DAF15A0F92}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {521BD444-857E-4C9E-A6C4-4467470337E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {521BD444-857E-4C9E-A6C4-4467470337E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {521BD444-857E-4C9E-A6C4-4467470337E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {521BD444-857E-4C9E-A6C4-4467470337E4}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {72BA2FF2-D5F9-4442-9E18-B90DC9DED11F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {72BA2FF2-D5F9-4442-9E18-B90DC9DED11F}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {72BA2FF2-D5F9-4442-9E18-B90DC9DED11F}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {72BA2FF2-D5F9-4442-9E18-B90DC9DED11F}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {AFFA19AA-28A3-4580-BF08-A4F8EEAE5FFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {AFFA19AA-28A3-4580-BF08-A4F8EEAE5FFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {AFFA19AA-28A3-4580-BF08-A4F8EEAE5FFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {AFFA19AA-28A3-4580-BF08-A4F8EEAE5FFB}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {327D6D12-3F70-4F7F-BAAB-02518C01A86F}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/src/TaskHandler/TaskDetails.cs:
--------------------------------------------------------------------------------
1 | namespace TaskHandler
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | ///
9 | /// Task details.
10 | ///
11 | public class TaskDetails
12 | {
13 | #region Public-Members
14 |
15 | ///
16 | /// GUID.
17 | ///
18 | public Guid Guid { get; set; } = Guid.NewGuid();
19 |
20 | ///
21 | /// User-supplied name.
22 | ///
23 | public string Name { get; set; } = null;
24 |
25 | ///
26 | /// User-supplied metadata.
27 | ///
28 | public Dictionary Metadata
29 | {
30 | get
31 | {
32 | return _Metadata;
33 | }
34 | set
35 | {
36 | if (value == null) _Metadata = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
37 | else _Metadata = value;
38 | }
39 | }
40 |
41 | ///
42 | /// Action.
43 | ///
44 | public Func Function { get; set; }
45 |
46 | ///
47 | /// Task.
48 | ///
49 | public Task Task { get; set; }
50 |
51 | ///
52 | /// Token source.
53 | ///
54 | public CancellationTokenSource TokenSource { get; set; } = new CancellationTokenSource();
55 |
56 | ///
57 | /// Token.
58 | ///
59 | public CancellationToken Token { get; set; }
60 |
61 | #endregion
62 |
63 | #region Private-Members
64 |
65 | private Dictionary _Metadata = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
66 |
67 | #endregion
68 |
69 | #region Constructors-and-Factories
70 |
71 | ///
72 | /// Instantiate.
73 | ///
74 | public TaskDetails()
75 | {
76 | Token = TokenSource.Token;
77 | }
78 |
79 | #endregion
80 |
81 | #region Public-Methods
82 |
83 | #endregion
84 |
85 | #region Private-Methods
86 |
87 | #endregion
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/TaskHandler/TaskHandler.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.0.9
5 | netstandard2.0;netstandard2.1;net6.0;net8.0
6 | logo.ico
7 | True
8 | .\TaskHandler.xml
9 | TaskHandler
10 | Joel Christner
11 | A simple C# class library to help manage running a queue of tasks without relinquishing control.
12 | (c)2025 Joel Christner
13 | https://github.com/jchristn/TaskHandler
14 | logo.png
15 | README.md
16 | https://github.com/jchristn/TaskHandler
17 | github
18 | task queue work scheduler
19 | Retarget to include net8.0.
20 | LICENSE.md
21 | False
22 | True
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | True
32 | \
33 |
34 |
35 | True
36 | \
37 |
38 |
39 | True
40 | \
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/TaskHandler/TaskHandler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TaskHandler
5 |
6 |
7 |
8 |
9 | Task details.
10 |
11 |
12 |
13 |
14 | GUID.
15 |
16 |
17 |
18 |
19 | User-supplied name.
20 |
21 |
22 |
23 |
24 | User-supplied metadata.
25 |
26 |
27 |
28 |
29 | Action.
30 |
31 |
32 |
33 |
34 | Task.
35 |
36 |
37 |
38 |
39 | Token source.
40 |
41 |
42 |
43 |
44 | Token.
45 |
46 |
47 |
48 |
49 | Instantiate.
50 |
51 |
52 |
53 |
54 | Task queue.
55 |
56 |
57 |
58 |
59 | Method to invoke to send log messages.
60 |
61 |
62 |
63 |
64 | Maximum number of concurrent tasks.
65 |
66 |
67 |
68 |
69 | Number of running tasks.
70 |
71 |
72 |
73 |
74 | Running tasks dictionary.
75 |
76 |
77 |
78 |
79 | Queue containing tasks waiting to be started.
80 |
81 |
82 |
83 |
84 | Event to fire when a task is added.
85 |
86 |
87 |
88 |
89 | Event to fire when a task is started.
90 |
91 |
92 |
93 |
94 | Event to fire when a task is canceled.
95 |
96 |
97 |
98 |
99 | Event to fire when a task is faulted.
100 |
101 |
102 |
103 |
104 | Event to fire when a task is finished.
105 |
106 |
107 |
108 |
109 | Event to fire when processing starts.
110 |
111 |
112 |
113 |
114 | Event to fire when processing stops.
115 |
116 |
117 |
118 |
119 | Boolean to indicate whether or not the task runner is running.
120 |
121 |
122 |
123 |
124 | Iteration delay when checking for additional tasks to run.
125 |
126 |
127 |
128 |
129 | Instantiate.
130 |
131 | Maximum concurrent tasks.
132 |
133 |
134 |
135 | Dispose.
136 |
137 |
138 |
139 |
140 | Add a task.
141 |
142 | Guid.
143 | Name of the task.
144 | Dictionary containing metadata.
145 | Action.
146 | TaskDetails.
147 |
148 |
149 |
150 | Start running tasks.
151 |
152 |
153 |
154 |
155 | Stop running tasks.
156 |
157 |
158 |
159 |
160 | Stop a running task by GUID.
161 |
162 | GUID.
163 |
164 |
165 |
166 | Task runner with timeout.
167 |
168 |
169 |
170 |
171 | Method to invoke to send log messages.
172 |
173 |
174 |
175 |
176 | Message header to prepend to each emitted log message.
177 |
178 |
179 |
180 |
181 | Run a task with a given timeout.
182 |
183 | Result type.
184 | Task.
185 | Timeout in milliseconds.
186 | Cancellation token source.
187 | Task.
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/src/TaskHandler/TaskQueue.cs:
--------------------------------------------------------------------------------
1 | namespace TaskHandler
2 | {
3 | using System;
4 | using System.Collections.Concurrent;
5 | using System.Collections.Generic;
6 | using System.ComponentModel.Design;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | ///
11 | /// Task queue.
12 | ///
13 | public class TaskQueue : IDisposable
14 | {
15 | #region Public-Members
16 |
17 | ///
18 | /// Method to invoke to send log messages.
19 | ///
20 | public Action Logger { get; set; } = null;
21 |
22 | ///
23 | /// Maximum number of concurrent tasks.
24 | ///
25 | public int MaxConcurrentTasks
26 | {
27 | get
28 | {
29 | return _MaxConcurrentTasks;
30 | }
31 | set
32 | {
33 | if (value < 1) throw new ArgumentOutOfRangeException(nameof(MaxConcurrentTasks));
34 | _MaxConcurrentTasks = value;
35 | }
36 | }
37 |
38 | ///
39 | /// Number of running tasks.
40 | ///
41 | public int RunningCount
42 | {
43 | get
44 | {
45 | return _RunningTasks.Count;
46 | }
47 | }
48 |
49 | ///
50 | /// Running tasks dictionary.
51 | ///
52 | public ConcurrentDictionary RunningTasks
53 | {
54 | get
55 | {
56 | return _RunningTasks;
57 | }
58 | }
59 |
60 | ///
61 | /// Queue containing tasks waiting to be started.
62 | ///
63 | public ConcurrentQueue QueuedTasks
64 | {
65 | get
66 | {
67 | return _QueuedTasks;
68 | }
69 | }
70 |
71 | ///
72 | /// Event to fire when a task is added.
73 | ///
74 | public EventHandler OnTaskAdded { get; set; }
75 |
76 | ///
77 | /// Event to fire when a task is started.
78 | ///
79 | public EventHandler OnTaskStarted { get; set; }
80 |
81 | ///
82 | /// Event to fire when a task is canceled.
83 | ///
84 | public EventHandler OnTaskCanceled { get; set; }
85 |
86 | ///
87 | /// Event to fire when a task is faulted.
88 | ///
89 | public EventHandler OnTaskFaulted { get; set; }
90 |
91 | ///
92 | /// Event to fire when a task is finished.
93 | ///
94 | public EventHandler OnTaskFinished { get; set; }
95 |
96 | ///
97 | /// Event to fire when processing starts.
98 | ///
99 | public EventHandler OnProcessingStarted { get; set; }
100 |
101 | ///
102 | /// Event to fire when processing stops.
103 | ///
104 | public EventHandler OnProcessingStopped { get; set; }
105 |
106 | ///
107 | /// Boolean to indicate whether or not the task runner is running.
108 | ///
109 | public bool IsRunning
110 | {
111 | get
112 | {
113 | return (_TaskRunner != null && _TaskRunner.Status == TaskStatus.Running);
114 | }
115 | }
116 |
117 | ///
118 | /// Iteration delay when checking for additional tasks to run.
119 | ///
120 | public int IterationDelayMs
121 | {
122 | get
123 | {
124 | return _IterationDelayMs;
125 | }
126 | set
127 | {
128 | if (value < 1) throw new ArgumentOutOfRangeException(nameof(IterationDelayMs));
129 | _IterationDelayMs = value;
130 | }
131 | }
132 |
133 | #endregion
134 |
135 | #region Private-Members
136 |
137 | private string _Header = "[TaskHandler] ";
138 | private int _MaxConcurrentTasks = 32;
139 | private ConcurrentDictionary _RunningTasks = new ConcurrentDictionary();
140 | private ConcurrentQueue _QueuedTasks = new ConcurrentQueue();
141 |
142 | private CancellationTokenSource _TaskRunnerTokenSource = new CancellationTokenSource();
143 | private CancellationToken _TaskRunnerToken;
144 | private Task _TaskRunner = null;
145 |
146 | private int _IterationDelayMs = 100;
147 |
148 | #endregion
149 |
150 | #region Constructors-and-Factories
151 |
152 | ///
153 | /// Instantiate.
154 | ///
155 | /// Maximum concurrent tasks.
156 | public TaskQueue(int maxConcurrentTasks = 32)
157 | {
158 | _TaskRunnerToken = _TaskRunnerTokenSource.Token;
159 |
160 | MaxConcurrentTasks = maxConcurrentTasks;
161 | }
162 |
163 | #endregion
164 |
165 | #region Public-Methods
166 |
167 | ///
168 | /// Dispose.
169 | ///
170 | public void Dispose()
171 | {
172 | Logger?.Invoke(_Header + "disposing");
173 |
174 | foreach (KeyValuePair task in _RunningTasks)
175 | {
176 | Logger?.Invoke(_Header + "canceling task GUID " + task.Key.ToString());
177 | task.Value.TokenSource.Cancel();
178 | }
179 |
180 | if (!_TaskRunnerTokenSource.IsCancellationRequested)
181 | {
182 | _TaskRunnerTokenSource.Cancel();
183 | }
184 |
185 | _RunningTasks = null;
186 | _QueuedTasks = null;
187 | }
188 |
189 | ///
190 | /// Add a task.
191 | ///
192 | /// Guid.
193 | /// Name of the task.
194 | /// Dictionary containing metadata.
195 | /// Action.
196 | /// TaskDetails.
197 | public TaskDetails AddTask(Guid guid, string name, Dictionary metadata, Func func)
198 | {
199 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
200 | if (func == null) throw new ArgumentNullException(nameof(func));
201 | TaskDetails details = new TaskDetails
202 | {
203 | Guid = guid,
204 | Name = name,
205 | Metadata = metadata,
206 | Function = func
207 | };
208 |
209 | _QueuedTasks.Enqueue(details);
210 | OnTaskAdded?.Invoke(this, details);
211 | return details;
212 | }
213 |
214 | ///
215 | /// Start running tasks.
216 | ///
217 | public void Start()
218 | {
219 | if (_TaskRunner != null) throw new InvalidOperationException("Task runner is already started.");
220 | Logger?.Invoke(_Header + "starting");
221 | _TaskRunner = Task.Run(() => TaskRunner(_TaskRunnerToken), _TaskRunnerToken);
222 | OnProcessingStarted?.Invoke(this, EventArgs.Empty);
223 | }
224 |
225 | ///
226 | /// Stop running tasks.
227 | ///
228 | public void Stop()
229 | {
230 | Logger?.Invoke(_Header + "stopping (" + _RunningTasks.Count + " running tasks)");
231 | if (_TaskRunner == null) return;
232 | if (_RunningTasks.Count > 0)
233 | {
234 | foreach (KeyValuePair task in _RunningTasks)
235 | {
236 | Logger?.Invoke(_Header + "evaluating task " + task.Key.ToString());
237 | if (!task.Value.TokenSource.IsCancellationRequested)
238 | {
239 | Logger?.Invoke(_Header + "canceling task " + task.Key.ToString());
240 | task.Value.TokenSource.Cancel();
241 | OnTaskCanceled?.Invoke(this, task.Value);
242 | }
243 | }
244 | }
245 |
246 | if (!_TaskRunnerTokenSource.IsCancellationRequested)
247 | {
248 | _TaskRunnerTokenSource.Cancel();
249 | }
250 |
251 | OnProcessingStopped?.Invoke(this, EventArgs.Empty);
252 | _TaskRunner = null;
253 | }
254 |
255 | ///
256 | /// Stop a running task by GUID.
257 | ///
258 | /// GUID.
259 | public void Stop(Guid guid)
260 | {
261 | Logger?.Invoke(_Header + "attempting to stop task " + guid.ToString());
262 |
263 | if (_TaskRunner == null) return;
264 |
265 | TaskDetails task = null;
266 | if (_RunningTasks.TryGetValue(guid, out task))
267 | {
268 | Logger?.Invoke(_Header + "canceling task " + guid.ToString());
269 | task.TokenSource.Cancel();
270 | OnTaskCanceled?.Invoke(this, task);
271 | }
272 | else
273 | {
274 | Logger?.Invoke(_Header + "task " + guid.ToString() + " not found");
275 | }
276 | }
277 |
278 | #endregion
279 |
280 | #region Private-Methods
281 |
282 | private async Task TaskRunner(CancellationToken token)
283 | {
284 | while (!token.IsCancellationRequested)
285 | {
286 | try
287 | {
288 | await Task.Delay(_IterationDelayMs, token);
289 |
290 | #region Dequeue-and-Run
291 |
292 | if ((RunningCount < MaxConcurrentTasks)
293 | && _QueuedTasks.Count > 0)
294 | {
295 | TaskDetails task;
296 | if (!_QueuedTasks.TryDequeue(out task))
297 | {
298 | Logger?.Invoke(_Header + "could not dequeue task from queue");
299 | continue;
300 | }
301 | else
302 | {
303 | Logger?.Invoke(_Header + "dequeued task " + task.Guid.ToString());
304 | }
305 |
306 | _RunningTasks.TryAdd(task.Guid, task);
307 |
308 | task.Task = Task.Run(() => task.Function(task.Token), task.Token);
309 | Logger?.Invoke(_Header + "started task " + task.Guid.ToString() + " (" + _RunningTasks.Count + " running tasks)");
310 | OnTaskStarted?.Invoke(this, task);
311 | }
312 |
313 | #endregion
314 |
315 | #region Check-for-Completed-Tasks
316 |
317 | if (_RunningTasks.Count > 0)
318 | {
319 | ConcurrentDictionary updatedRunningTasks = new ConcurrentDictionary();
320 |
321 | foreach (KeyValuePair task in _RunningTasks)
322 | {
323 | // Logger?.Invoke(_Header + "task " + task.Key.ToString() + " status: " + task.Value.Task.Status.ToString());
324 |
325 | if (task.Value.Task.Status == TaskStatus.Created
326 | || task.Value.Task.Status == TaskStatus.Running
327 | || task.Value.Task.Status == TaskStatus.WaitingForActivation
328 | || task.Value.Task.Status == TaskStatus.WaitingForChildrenToComplete
329 | || task.Value.Task.Status == TaskStatus.WaitingToRun)
330 | {
331 | // Logger?.Invoke(_Header + "task " + task.Key.ToString() + " still running");
332 | updatedRunningTasks.TryAdd(task.Key, task.Value);
333 | }
334 | else if (task.Value.Task.Status == TaskStatus.RanToCompletion)
335 | {
336 | // do not add it to the updated list
337 | Logger?.Invoke(_Header + "task " + task.Key.ToString() + " completed");
338 | OnTaskFinished?.Invoke(this, task.Value);
339 | }
340 | else if (task.Value.Task.Status == TaskStatus.Faulted)
341 | {
342 | // do not add it to the updated list
343 | Logger?.Invoke(_Header + "task " + task.Key.ToString() + " faulted");
344 | OnTaskFaulted?.Invoke(this, task.Value);
345 | }
346 | else if (task.Value.Task.Status == TaskStatus.Canceled)
347 | {
348 | // do not add it to the updated list
349 | Logger?.Invoke(_Header + "task " + task.Key.ToString() + " canceled");
350 | OnTaskCanceled?.Invoke(this, task.Value);
351 | }
352 | }
353 |
354 | _RunningTasks = updatedRunningTasks;
355 | }
356 |
357 | #endregion
358 | }
359 | catch (TaskCanceledException)
360 | {
361 | Logger?.Invoke(_Header + "task runner canceled");
362 | OnProcessingStopped?.Invoke(this, EventArgs.Empty);
363 | Dispose();
364 |
365 | }
366 | catch (OperationCanceledException)
367 | {
368 | Logger?.Invoke(_Header + "task runner canceled");
369 | OnProcessingStopped?.Invoke(this, EventArgs.Empty);
370 | Dispose();
371 | }
372 | catch (Exception e)
373 | {
374 | Logger?.Invoke(_Header + "task runner exception: " + Environment.NewLine + e.ToString());
375 | OnProcessingStopped?.Invoke(this, EventArgs.Empty);
376 | }
377 | }
378 |
379 | Logger?.Invoke(_Header + "task runner exiting");
380 | }
381 |
382 | #endregion
383 | }
384 | }
--------------------------------------------------------------------------------
/src/TaskHandler/TaskRunWithTimeout.cs:
--------------------------------------------------------------------------------
1 | namespace TaskHandler
2 | {
3 | using System;
4 | using System.Text;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | ///
9 | /// Task runner with timeout.
10 | ///
11 | public static class TaskRunWithTimeout
12 | {
13 | ///
14 | /// Method to invoke to send log messages.
15 | ///
16 | public static Action Logger { get; set; } = null;
17 |
18 | ///
19 | /// Message header to prepend to each emitted log message.
20 | ///
21 | public static string LogHeader { get; set; } = "[TaskRunWithTimeout] ";
22 |
23 | ///
24 | /// Run a task with a given timeout.
25 | ///
26 | /// Result type.
27 | /// Task.
28 | /// Timeout in milliseconds.
29 | /// Cancellation token source.
30 | /// Task.
31 | public static async Task Go(Task task, int timeoutMs, CancellationTokenSource tokenSource)
32 | {
33 | if (timeoutMs < 1) throw new ArgumentOutOfRangeException(nameof(timeoutMs));
34 | if (tokenSource == null) throw new ArgumentNullException(nameof(tokenSource));
35 |
36 | if (await Task.WhenAny(new Task[] { task, Task.Delay(timeoutMs) }) == task)
37 | {
38 | Log("user task completed within the timeout window");
39 | return task.Result;
40 | }
41 | else
42 | {
43 | if (!tokenSource.IsCancellationRequested)
44 | {
45 | Log("cancellation has not yet been requested, requesting");
46 | tokenSource.Cancel();
47 | }
48 | else
49 | {
50 | Log("cancellation has already been requested, skipping");
51 | }
52 |
53 | Log("timeout task completed before user task (user task timed out)");
54 | throw new TimeoutException("The specified task timed out after " + timeoutMs + "ms.");
55 | }
56 | }
57 |
58 | private static void Log(string msg)
59 | {
60 | if (String.IsNullOrEmpty(msg)) return;
61 |
62 | StringBuilder sb = new StringBuilder();
63 | if (!String.IsNullOrEmpty(LogHeader))
64 | {
65 | if (LogHeader.EndsWith(" ")) sb.Append(LogHeader);
66 | else sb.Append(LogHeader + " ");
67 | }
68 |
69 | sb.Append(msg);
70 | Logger?.Invoke(sb.ToString());
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/TaskHandler/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/TaskHandler/37a38c8af340cec1b3a86b94b3dc032f9fa224aa/src/TaskHandler/logo.ico
--------------------------------------------------------------------------------
/src/Test.RunWithTimeout/Program.cs:
--------------------------------------------------------------------------------
1 | namespace Test.RunWithTimeout
2 | {
3 | using System;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using GetSomeInput;
7 | using TaskHandler;
8 |
9 | public static class Program
10 | {
11 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
12 |
13 | public static async Task Main(string[] args)
14 | {
15 | // string result = null;
16 | Person result1 = null;
17 | int delay;
18 |
19 | CancellationTokenSource tokenSource = new CancellationTokenSource();
20 | CancellationToken token = tokenSource.Token;
21 |
22 | for (int i = 0; i < 5; i++)
23 | {
24 | try
25 | {
26 | if (i % 2 == 0) delay = 3000;
27 | else delay = 500;
28 |
29 | Func> task1 = async (CancellationToken token) =>
30 | {
31 | try
32 | {
33 | await Task.Delay(delay);
34 | // return "hello from " + i + "!";
35 | return new Person { FirstName = "Hello", LastName = i.ToString() };
36 | }
37 | catch (Exception e)
38 | {
39 | Console.WriteLine(e.ToString());
40 | return null;
41 | }
42 | };
43 |
44 | result1 = await TaskRunWithTimeout.Go(task1(token), 2500, tokenSource);
45 |
46 | if (result1 != null)
47 | Console.WriteLine(i + ": " + result1.FirstName + " " + result1.LastName);
48 | else
49 | Console.WriteLine(i + ": null");
50 | }
51 | catch (TimeoutException)
52 | {
53 | Console.WriteLine(i + ": timeout");
54 | }
55 | }
56 |
57 | Console.WriteLine("");
58 | string input = Inputty.GetString("Text to echo:", null, false);
59 |
60 | Func> task2 = async (string text, CancellationToken token) =>
61 | {
62 | return text;
63 | };
64 |
65 | string result2 = await TaskRunWithTimeout.Go(task2(input, token), 2500, tokenSource);
66 | Console.WriteLine("Returned: " + result2);
67 |
68 | Console.WriteLine("");
69 | }
70 |
71 | public class Person
72 | {
73 | public string FirstName { get; set; }
74 | public string LastName { get; set; }
75 | }
76 |
77 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
78 | }
79 | }
--------------------------------------------------------------------------------
/src/Test.RunWithTimeout/Test.RunWithTimeout.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0;net8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Test.RunWithTimeoutHttp/Program.cs:
--------------------------------------------------------------------------------
1 | namespace Test.RunWithTimeoutHttp
2 | {
3 | using System;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using TaskHandler;
7 | using Timestamps;
8 | using GetSomeInput;
9 | using RestWrapper;
10 |
11 | public static class Program
12 | {
13 | public static async Task Main(string[] args)
14 | {
15 | string url = Inputty.GetString("URL:", "http://localhost:8888", false);
16 | int count = Inputty.GetInteger("Count:", 100, true, false);
17 |
18 | for (int i = 0; i < count; i++)
19 | {
20 | await Task.Delay(6000);
21 |
22 | int counter = i; // to prevent multiple messages pointing to the same iteration
23 | Console.WriteLine("---" + Environment.NewLine + "Starting iteration " + counter);
24 |
25 | CancellationTokenSource cts = new CancellationTokenSource();
26 | CancellationToken token = cts.Token;
27 |
28 | using (Timestamp ts = new Timestamp())
29 | {
30 | try
31 | {
32 | TaskRunWithTimeout.Logger = Console.WriteLine;
33 |
34 | Func> restRequest = async (CancellationToken token) =>
35 | {
36 | try
37 | {
38 | using (RestRequest req = new RestRequest(url))
39 | {
40 | req.Logger = Console.WriteLine;
41 |
42 | using (RestResponse resp = await req.SendAsync(token))
43 | {
44 | if (resp == null)
45 | {
46 | Console.WriteLine(counter + ": (null response)");
47 | return null;
48 | }
49 | else
50 | {
51 | Console.WriteLine(counter + ": " + resp.StatusCode + " (" + ts.TotalMs + "ms)");
52 | return resp.DataAsString;
53 | }
54 | }
55 | }
56 | }
57 | catch (Exception eInner)
58 | {
59 | Console.WriteLine(counter + ": inner exception: " + eInner.Message);
60 | throw;
61 | }
62 | };
63 |
64 | await TaskRunWithTimeout.Go(restRequest(token), 2500, cts);
65 | }
66 | catch (TimeoutException)
67 | {
68 | Console.WriteLine(counter + ": timeout after " + ts.TotalMs + "ms");
69 | }
70 | catch (Exception eOuter)
71 | {
72 | Console.WriteLine(counter + ": outer exception: " + eOuter.ToString());
73 | }
74 | }
75 | }
76 | }
77 |
78 | public class Person
79 | {
80 | public string FirstName { get; set; }
81 | public string LastName { get; set; }
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/src/Test.RunWithTimeoutHttp/Test.RunWithTimeoutHttp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0;net8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/Test/Program.cs:
--------------------------------------------------------------------------------
1 | namespace Test
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using GetSomeInput;
8 | using TaskHandler;
9 |
10 | public static class Program
11 | {
12 | private static bool _RunForever = true;
13 | private static TaskQueue _TaskQueue = new TaskQueue();
14 | private static int _NumTasks = 6;
15 |
16 | public static void Main(string[] args)
17 | {
18 | /*
19 | _TaskQueue.Logger = Console.WriteLine;
20 | _TaskQueue.OnTaskAdded += OnTaskAdded;
21 | _TaskQueue.OnTaskStarted += OnTaskStarted;
22 | _TaskQueue.OnTaskFinished += OnTaskFinished;
23 | _TaskQueue.OnTaskFaulted += OnTaskFaulted;
24 | _TaskQueue.OnTaskCanceled += OnTaskCanceled;
25 | _TaskQueue.OnProcessingStarted += OnProcessingStarted;
26 | _TaskQueue.OnProcessingStopped += OnProcessingStopped;
27 | */
28 |
29 | while (_RunForever)
30 | {
31 | string userInput = Inputty.GetString("Command [?/help]:", null, false);
32 | switch (userInput)
33 | {
34 | case "q":
35 | _RunForever = false;
36 | break;
37 | case "c":
38 | case "cls":
39 | Console.Clear();
40 | break;
41 | case "?":
42 | Menu();
43 | break;
44 | case "add":
45 | AddTasks();
46 | break;
47 | case "queue":
48 | ShowQueue();
49 | break;
50 | case "start":
51 | _TaskQueue.Start();
52 | break;
53 | case "stop all":
54 | _TaskQueue.Stop();
55 | break;
56 | case "stop":
57 | StopTask();
58 | break;
59 | case "running":
60 | Console.WriteLine(_TaskQueue.RunningCount + " running task(s)");
61 | break;
62 | }
63 | }
64 | }
65 |
66 | private static void OnProcessingStopped(object sender, EventArgs e)
67 | {
68 | Console.WriteLine("*** Processing stopped");
69 | }
70 |
71 | private static void OnProcessingStarted(object sender, EventArgs e)
72 | {
73 | Console.WriteLine("*** Processing started");
74 | }
75 |
76 | private static void OnTaskAdded(object sender, TaskDetails e)
77 | {
78 | Console.WriteLine("*** Task added: " + e.Name);
79 | }
80 |
81 | private static void OnTaskFinished(object sender, TaskDetails e)
82 | {
83 | Console.WriteLine("*** Task finished: " + e.Name);
84 | }
85 |
86 | private static void OnTaskStarted(object sender, TaskDetails e)
87 | {
88 | Console.WriteLine("*** Task started: " + e.Name);
89 | }
90 |
91 | private static void OnTaskCanceled(object sender, TaskDetails e)
92 | {
93 | Console.WriteLine("*** Task canceled: " + e.Name);
94 | }
95 |
96 | private static void OnTaskFaulted(object sender, TaskDetails e)
97 | {
98 | Console.WriteLine("*** Task faulted: " + e.Name);
99 | }
100 |
101 | private static void Menu()
102 | {
103 | Console.WriteLine("");
104 | Console.WriteLine("Available commands:");
105 | Console.WriteLine(" q Quit this program");
106 | Console.WriteLine(" cls Clear the screen");
107 | Console.WriteLine(" ? Help, this menu");
108 | Console.WriteLine(" add Add " + _NumTasks + " tasks that wait 5, 10, 20, or 30 seconds");
109 | Console.WriteLine(" queue Display the current queue of tasks waiting to execute");
110 | Console.WriteLine(" start Start processing tasks");
111 | Console.WriteLine(" stop all Stop all running tasks");
112 | Console.WriteLine(" stop Stop a specific task");
113 | Console.WriteLine(" running Show number of running tasks");
114 | Console.WriteLine("");
115 | }
116 |
117 | private static void AddTasks()
118 | {
119 | for (int i = 0; i < _NumTasks; i++)
120 | {
121 | int wait = 5000;
122 | if (i % 2 == 0) wait = 10000;
123 | else if (i % 3 == 0) wait = 20000;
124 | else if (i % 5 == 0) wait = 30000;
125 |
126 | Guid guid = Guid.NewGuid();
127 |
128 | string name = "Task." + guid.ToString() + "." + wait.ToString();
129 |
130 | Func action1 = async (CancellationToken token) =>
131 | {
132 | Console.WriteLine(name + " running");
133 | try
134 | {
135 | int waited = 0;
136 | while (true)
137 | {
138 | if (token.IsCancellationRequested)
139 | {
140 | Console.WriteLine("!!! " + name + " cancellation requested");
141 | break;
142 | }
143 | await Task.Delay(100, token);
144 | waited += 100;
145 | if (waited >= wait) break;
146 | }
147 | Console.WriteLine(name + " finished");
148 | }
149 | catch (TaskCanceledException)
150 | {
151 | Console.WriteLine(name + " task canceled exception");
152 | }
153 | catch (OperationCanceledException)
154 | {
155 | Console.WriteLine(name + " operation canceled exception");
156 | }
157 | catch (Exception e)
158 | {
159 | Console.WriteLine("Exception in task GUID " + guid.ToString() + ":" + Environment.NewLine + e.ToString());
160 | }
161 | };
162 |
163 | _TaskQueue.AddTask(guid, name, new Dictionary(), action1);
164 | }
165 | }
166 |
167 | private static void ShowQueue()
168 | {
169 | Console.WriteLine("");
170 | Console.WriteLine("Tasks in queue: " + _TaskQueue.QueuedTasks.Count);
171 |
172 | foreach (TaskDetails task in _TaskQueue.QueuedTasks)
173 | {
174 | Console.WriteLine(task.Guid.ToString() + ": " + task.Name);
175 | }
176 |
177 | Console.WriteLine("");
178 | }
179 |
180 | private static void StopTask()
181 | {
182 | string guidStr = Inputty.GetString("GUID:", null, true);
183 | if (String.IsNullOrEmpty(guidStr)) return;
184 |
185 | if (Guid.TryParse(guidStr, out Guid guid))
186 | {
187 | _TaskQueue.Stop(guid);
188 | }
189 | }
190 | }
191 | }
--------------------------------------------------------------------------------
/src/Test/Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0;net8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------