├── .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 | [![NuGet Version](https://img.shields.io/nuget/v/TaskHandler.svg?style=flat)](https://www.nuget.org/packages/TaskHandler/) [![NuGet](https://img.shields.io/nuget/dt/TaskHandler.svg)](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 | --------------------------------------------------------------------------------