├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── build.yml ├── .gitignore ├── AsyncEx.128.png ├── CONTRIBUTING.md ├── LICENSE ├── Nito.AsyncEx.sln ├── README.md ├── doc ├── ApmAsyncFactory.md ├── AsyncAutoResetEvent.md ├── AsyncCollection.md ├── AsyncConditionVariable.md ├── AsyncContext.md ├── AsyncCountdownEvent.md ├── AsyncLazy.md ├── AsyncLock.md ├── AsyncManualResetEvent.md ├── AsyncMonitor.md ├── AsyncProducerConsumerQueue.md ├── AsyncReaderWriterLock.md ├── AsyncSemaphore.md ├── Cancellation.md ├── ExceptionHelpers.md ├── Home.md ├── TaskCompletionSourceExtensions.md ├── TaskConstants.md ├── TaskExtensions.md ├── TaskFactoryExtensions.md ├── design.md └── upgrade.md ├── src ├── Directory.Build.props ├── Directory.Build.targets ├── Nito.AsyncEx.Context │ ├── AsyncContext.SynchronizationContext.cs │ ├── AsyncContext.TaskQueue.cs │ ├── AsyncContext.TaskScheduler.cs │ ├── AsyncContext.cs │ ├── AsyncContextThread.cs │ ├── Nito.AsyncEx.Context.csproj │ └── SynchronizationContextSwitcher.cs ├── Nito.AsyncEx.Coordination │ ├── AsyncAutoResetEvent.cs │ ├── AsyncCollection.cs │ ├── AsyncConditionVariable.cs │ ├── AsyncCountdownEvent.cs │ ├── AsyncLazy.cs │ ├── AsyncLock.cs │ ├── AsyncManualResetEvent.cs │ ├── AsyncMonitor.cs │ ├── AsyncProducerConsumerQueue.cs │ ├── AsyncReaderWriterLock.cs │ ├── AsyncSemaphore.cs │ ├── AsyncWaitQueue.cs │ ├── IdManager.cs │ ├── Nito.AsyncEx.Coordination.csproj │ └── PauseToken.cs ├── Nito.AsyncEx.Interop.WaitHandles │ ├── Interop │ │ └── WaitHandleAsyncFactory.cs │ └── Nito.AsyncEx.Interop.WaitHandles.csproj ├── Nito.AsyncEx.Oop │ ├── DeferralManager.cs │ └── Nito.AsyncEx.Oop.csproj ├── Nito.AsyncEx.Tasks │ ├── AwaitableDisposable.cs │ ├── CancellationTokenTaskSource.cs │ ├── ExceptionHelpers.cs │ ├── Interop │ │ ├── ApmAsyncFactory.cs │ │ ├── EventArguments.cs │ │ └── EventAsyncFactory.cs │ ├── Nito.AsyncEx.Tasks.csproj │ ├── SemaphoreSlimExtensions.cs │ ├── SynchronizationContextExtensions.cs │ ├── SynchronizationContextSwitcher.cs │ ├── Synchronous │ │ └── TaskExtensions.cs │ ├── TaskCompletionSourceExtensions.cs │ ├── TaskConstants.cs │ ├── TaskExtensions.cs │ ├── TaskFactoryExtensions.cs │ └── TaskHelper.cs ├── Nito.AsyncEx │ └── Nito.AsyncEx.csproj ├── icon.png └── project.props └── test ├── AsyncEx.Context.UnitTests ├── AsyncContextThreadUnitTests.cs ├── AsyncContextUnitTests.cs └── AsyncEx.Context.UnitTests.csproj ├── AsyncEx.Coordination.UnitTests ├── AsyncAutoResetEventUnitTests.cs ├── AsyncConditionVariableUnitTests.cs ├── AsyncCountdownEventUnitTests.cs ├── AsyncEx.Coordination.UnitTests.csproj ├── AsyncLazyUnitTests.cs ├── AsyncLockUnitTests.cs ├── AsyncManualResetEventUnitTests.cs ├── AsyncMonitorUnitTests.cs ├── AsyncProducerConsumerQueueUnitTests.cs ├── AsyncReaderWriterLockUnitTests.cs ├── AsyncSemaphoreUnitTests.cs └── AsyncWaitQueueUnitTests.cs ├── AsyncEx.Interop.WaitHandles.UnitTests ├── AsyncEx.Interop.WaitHandles.UnitTests.csproj └── WaitHandleInteropUnitTests.cs ├── AsyncEx.Oop.UnitTests ├── AsyncEx.Oop.UnitTests.csproj └── DeferralManagerUnitTests.cs ├── AsyncEx.Tasks.UnitTests ├── AsyncEx.Tasks.UnitTests.csproj ├── CancellationTokenTaskSourceUnitTests.cs ├── EventAsyncFactoryUnitTests.cs ├── SynchronousTaskExtensionsUnitTests.cs ├── TaskCompletionSourceExtensionsUnitTests.cs ├── TaskConstantsUnitTests.cs ├── TaskExtensionsUnitTests.cs └── TaskFactoryExtensionsUnitTests.cs └── Directory.Build.props /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1068: CancellationToken parameters must come last 4 | dotnet_diagnostic.CA1068.severity = silent 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [StephenCleary] 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # build.yml v1.4 2 | # 1.4 - Avoid set-env. 3 | # 1.3 - Include tag workflow in this file. 4 | # 1.2 - Define DOTNET_SKIP_FIRST_TIME_EXPERIENCE/NUGET_XMLDOC_MODE. 5 | # 1.1 - Use actions/cache@v2. 6 | # 1.0 - Initial release. 7 | 8 | name: Build 9 | 10 | on: 11 | - push 12 | 13 | env: 14 | CI: 'true' 15 | DOTNET_CLI_TELEMETRY_OPTOUT: 'true' 16 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 'true' 17 | NUGET_XMLDOC_MODE: 'skip' 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | steps: 23 | 24 | - name: Dump context 25 | env: 26 | CONTEXT: ${{ toJson(github) }} 27 | run: | 28 | echo "Context: $Env:CONTEXT" 29 | 30 | - name: Determine cache fallbacks 31 | if: github.event_name != 'push' 32 | id: cache_fallbacks 33 | run: | 34 | echo "::set-output name=nuget::nuget-" 35 | 36 | - name: Checkout 37 | uses: actions/checkout@v2 38 | with: 39 | fetch-depth: 0 40 | 41 | - name: Cache nuget 42 | uses: actions/cache@v2 43 | with: 44 | path: ~/.nuget/packages 45 | key: nuget-${{ hashFiles('**/*.csproj') }}-${{ hashFiles('**/*.props') }} 46 | restore-keys: ${{ steps.cache_fallbacks.outputs.nuget }} 47 | 48 | - name: Get existing tag 49 | id: existingtag 50 | uses: WyriHaximus/github-action-get-previous-tag@0.2.0 51 | continue-on-error: true 52 | 53 | - name: Get current version 54 | run: | 55 | dotnet tool install --global Nito.ProjProps 56 | echo "NEWTAG=v$(projprops --name version --output-format SingleValueOnly --project src --project-search)" >> $GITHUB_ENV 57 | 58 | - name: Build 59 | run: | 60 | dotnet build --configuration Release 61 | dotnet test --configuration Release --no-build --collect:"XPlat Code Coverage" 62 | dotnet pack --configuration Release --no-build 63 | 64 | - name: Upload package artifacts 65 | uses: actions/upload-artifact@v2 66 | with: 67 | name: nuget-packages 68 | path: | 69 | **/*.nupkg 70 | **/*.snupkg 71 | 72 | - name: Publish code coverage 73 | uses: codecov/codecov-action@v1 74 | 75 | - name: Publish packages 76 | if: env.NEWTAG != steps.existingtag.outputs.tag 77 | run: | 78 | dotnet nuget push **/*.nupkg --source https://api.nuget.org/v3/index.json --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }} 79 | 80 | - name: Create tag 81 | if: env.NEWTAG != steps.existingtag.outputs.tag 82 | run: | 83 | git tag ${{ env.NEWTAG }} 84 | git push --tags -------------------------------------------------------------------------------- /.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 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | packages/ 127 | ## TODO: If the tool you use requires repositories.config uncomment the next line 128 | #!packages/repositories.config 129 | 130 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 131 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 132 | !packages/build/ 133 | 134 | # Windows Azure Build Output 135 | csx/ 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.dbproj.schemaview 150 | *.pfx 151 | *.publishsettings 152 | node_modules/ 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | *.mdf 166 | *.ldf 167 | 168 | # Business Intelligence projects 169 | *.rdl.data 170 | *.bim.layout 171 | *.bim_*.settings 172 | 173 | # Microsoft Fakes 174 | FakesAssemblies/ 175 | 176 | .localhistory/ 177 | *.userprefs 178 | *.lock.json 179 | /.vs 180 | /tools 181 | -------------------------------------------------------------------------------- /AsyncEx.128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenCleary/AsyncEx/0361015459938f2eb8f3c1ad1021d19ee01c93a4/AsyncEx.128.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to AsyncEx 2 | 3 | :+1: Thanks for taking the time to contribute! :+1: 4 | 5 | If you have encountered a bug (or just have a question on how to use the library), feel free to open an issue immediately. If reporting a bug, please include the version of AsyncEx you are using, your runtime platform and version, and code to reproduce the problem. 6 | 7 | If you have an idea for code, or would like to contribute some code, the first step is to review the [design guidelines](doc/design.md). If your code falls within those guidelines, the next step is to open an issue for discussion. Finally, if the idea has been discussed and sounds like a good fit for AsyncEx, feel free to open a pull request. I know this is slow and annoying, but it makes me sad to close PRs that people put a lot of work into but don't fit in well with AsyncEx. 8 | 9 | Our code of conduct is: be nice. Seriously, I don't think we need lawyer language. Just be nice. :blush: 10 | 11 | That's it! Thanks again for contributing! :+1: -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 StephenCleary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](AsyncEx.128.png) 2 | 3 | # AsyncEx 4 | 5 | A helper library for async/await. 6 | 7 | Note: This README is for AsyncEx v5 (the current version). For AsyncEx v4, see [here](https://github.com/StephenCleary/AsyncEx/tree/v4). 8 | 9 | Supports `netstandard1.3` (including .NET 4.6, .NET Core 1.0, Xamarin.iOS 10, Xamarin.Android 7, Mono 4.6, and Universal Windows 10). 10 | 11 | [![NuGet Pre Release](https://img.shields.io/nuget/vpre/Nito.AsyncEx.svg)](https://www.nuget.org/packages/Nito.AsyncEx/) [![netstandard 1.3](https://img.shields.io/badge/netstandard-1.3-brightgreen.svg)](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) [![netstandard 2.0](https://img.shields.io/badge/netstandard-2.0-brightgreen.svg)](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) [![Code Coverage](https://coveralls.io/repos/github/StephenCleary/AsyncEx/badge.svg?branch=master)](https://coveralls.io/github/StephenCleary/AsyncEx?branch=master) [![Build status](https://ci.appveyor.com/api/projects/status/37edy7t2g377rojs/branch/master?svg=true)](https://ci.appveyor.com/project/StephenCleary/asyncex/branch/master) 12 | 13 | [![API docs](https://img.shields.io/badge/reference%20docs-api-blue.svg)](http://dotnetapis.com/pkg/Nito.AsyncEx) 14 | 15 | [Overview](doc/Home.md) - [Upgrade Guide](doc/upgrade.md) 16 | 17 | ## Getting Started 18 | 19 | Install the [NuGet package](http://www.nuget.org/packages/Nito.AsyncEx). 20 | 21 | ## AsyncLock 22 | 23 | A lot of developers start using this library for `AsyncLock`, an async-compatible mutual exclusion mechanism. Using `AsyncLock` is straightforward: 24 | 25 | ```C# 26 | private readonly AsyncLock _mutex = new AsyncLock(); 27 | public async Task UseLockAsync() 28 | { 29 | // AsyncLock can be locked asynchronously 30 | using (await _mutex.LockAsync()) 31 | { 32 | // It's safe to await while the lock is held 33 | await Task.Delay(TimeSpan.FromSeconds(1)); 34 | } 35 | } 36 | ``` 37 | 38 | `AsyncLock` also fully supports cancellation: 39 | 40 | ```C# 41 | public async Task UseLockAsync() 42 | { 43 | // Attempt to take the lock only for 2 seconds. 44 | var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2)); 45 | 46 | // If the lock isn't available after 2 seconds, this will 47 | // raise OperationCanceledException. 48 | using (await _mutex.LockAsync(cts.Token)) 49 | { 50 | await Task.Delay(TimeSpan.FromSeconds(1)); 51 | } 52 | } 53 | ``` 54 | 55 | `AsyncLock` also has a synchronous API. This permits some threads to acquire the lock asynchronously while other threads acquire the lock synchronously (blocking the thread). 56 | 57 | ```C# 58 | public async Task UseLockAsync() 59 | { 60 | using (await _mutex.LockAsync()) 61 | { 62 | await Task.Delay(TimeSpan.FromSeconds(1)); 63 | } 64 | } 65 | 66 | public void UseLock() 67 | { 68 | using (_mutex.Lock()) 69 | { 70 | Thread.Sleep(TimeSpan.FromSeconds(1)); 71 | } 72 | } 73 | ``` 74 | 75 | ## Other Coordination Primitives 76 | 77 | `AsyncLock` is just the beginning. The AsyncEx library contains a full suite of coordination primitives: `AsyncManualResetEvent`, `AsyncAutoResetEvent`, `AsyncConditionVariable`, `AsyncMonitor`, `AsyncSemaphore`, `AsyncCountdownEvent`, and `AsyncReaderWriterLock`. 78 | 79 | ## More Stuff 80 | 81 | There's quite a few other helpful types; see [the docs for full details](doc) 82 | 83 | ## Infrequently Asked Questions 84 | 85 | ### Older Platforms 86 | 87 | [AsyncEx v4](https://github.com/StephenCleary/AsyncEx/tree/v4) supported .NET 4.0, Windows Store 8.1, Windows Phone Silverlight 8.0, Windows Phone Applications 8.1, and Silverlight 5.0. Support for these platforms has been dropped with AsyncEx v5. 88 | 89 | AsyncEx v3 supported Windows Store 8.0, Windows Phone Silverlight 7.5, and Silverlight 4.0. Support for these platforms has been dropped with AsyncEx v4. 90 | -------------------------------------------------------------------------------- /doc/ApmAsyncFactory.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The `ApmAsyncFactory` type enables easy interoperation with the Asynchronous Programming Model (APM). 4 | 5 | APM is the old-style approach to asynchronous programming that used `Begin`/`End` method pairs with `IAsyncResult` representing the asynchronous operation. The `FromApm` methods on `ApmAsyncFactory` convert from APM to TAP, and the `ToBegin` and `ToEnd` methods convert from TAP to APM. 6 | -------------------------------------------------------------------------------- /doc/AsyncAutoResetEvent.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready equivalent of [AutoResetEvent](https://docs.microsoft.com/en-us/dotnet/api/system.threading.autoresetevent), similar to Stephen Toub's [AsyncAutoResetEvent](https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-2-asyncautoresetevent/). 4 | 5 | Like other "events", an `AsyncAutoResetEvent` is either **set** or **unset** at any time. An `AsyncAutoResetEvent` can be changed from **unset** to **set** by calling its `Set` method. When a `WaitAsync` operation completes, the `AsyncAutoResetEvent` is automatically changed back to the **unset** state. 6 | 7 | Moving an `AsyncAutoResetEvent` to the **set** state can only satisfy a single waiter. If there are multiple waiters when `Set` is called, only one will be released. (If this is not the behavior you want, use [AsyncManualResetEvent](AsyncManualResetEvent.md) instead). 8 | 9 | When an `AsyncAutoResetEvent` is in the **set** state (with no waiters), `Set` is a noop. The `AsyncAutoResetEvent` will not remember how many times `Set` is called; those extra signals are "lost". (If this is not the behavior you want, use [AsyncSemaphore](AsyncSemaphore.md) instead). 10 | 11 | The task returned from `WaitAsync` will enter the `Completed` state when the wait is satisfied and the `AsyncAutoResetEvent` has been automatically reset. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the `AsyncAutoResetEvent` has not been automatically reset. 12 | 13 | ## Advanced Usage 14 | 15 | You can call `WaitAsync` with an [already-cancelled `CancellationToken`](Cancellation.md) to attempt to acquire the `AsyncAutoResetEvent` immediately without actually entering the wait queue. 16 | -------------------------------------------------------------------------------- /doc/AsyncCollection.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | An `AsyncCollection` is an `async`-compatible wrapper around [IProducerConsumerCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.iproducerconsumercollection-1) collections such as [ConcurrentQueue](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1) or [ConcurrentBag](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentbag-1). 4 | 5 | This makes `AsyncCollection` an `async` near-equivalent of [BlockingCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1), which is a blocking wrapper around [IProducerConsumerCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.iproducerconsumercollection-1). 6 | -------------------------------------------------------------------------------- /doc/AsyncConditionVariable.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is an `async`-ready [condition variable](https://en.wikipedia.org/wiki/Condition_variable), a classical synchronization primitive in computer science without a direct .NET equivalent. 4 | 5 | An `AsyncConditionVariable` is associated with a single [AsyncLock](AsyncLock.md). All methods on the `AsyncConditionVariable` type ***require*** that you hold the associated lock before calling them. There's no runtime checks for these preconditions because there is no way to check them; you'll just have to be careful. I recommend you combine the `AsyncLock` with its associated `AsyncConditionVariable` instances into a higher-level custom lock/signal system. Note that the simple case of a single `AsyncLock` with a single `AsyncConditionVariable` is equivalent to [AsyncMonitor](AsyncMonitor.md). 6 | 7 | Waiting on an `AsyncConditionVariable` enables an `async` method to (asynchronously) wait for some condition to become true. While waiting, that `async` method gives up the `AsyncLock`. When the condition becomes true, another `async` method notifies the `AsyncConditionVariable`, which re-acquires the `AsyncLock` and releases the waiter. 8 | 9 | When notifying the `AsyncConditionVariable`, the notifying task may choose to release only a single waiter (`Notify`) or all waiters (`NotifyAll`). If there are no waiters, the notification is "lost"; it is not remembered by the `AsyncConditionVariable`. 10 | 11 | The task returned from `WaitAsync` will enter the `Completed` state when it receives a notification and re-acquires the `AsyncLock`. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the task will wait to enter the `Canceled` state until it re-acquires the `AsyncLock`. 12 | 13 | Remember that from the time `WaitAsync` is called to the time when its returned task completes, the `AsyncLock` is _not_ held by the calling task. 14 | 15 | Note that the correct logic for condition variables is to wait in a loop until the required condition is true. This is necessary because other tasks may execute between the notification and the completion of the wait. 16 | -------------------------------------------------------------------------------- /doc/AsyncContext.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The `AsyncContext` type provides a _context_ for executing asynchronous operations. The `await` keyword requires a _context_ to return back to. For most client programs, this is a UI context; for most server programs, this is a thread pool context. In most cases, you don't have to worry about it. 4 | 5 | However, Console applications and Win32 services use the thread pool context, and `AsyncContext` or `AsyncContextThread` could be useful in those situations. 6 | 7 | `AsyncContextThread` is a separate thread or task that runs an `AsyncContext`. `AsyncContextThread` does not derive from the `Thread` class. The thread begins running its `AsyncContext` immediately after creation. 8 | 9 | `AsyncContextThread` will stay in its loop until it is requested to exit by another thread calling `JoinAsync`. Disposing an `AsyncContextThread` will also request it to exit, but will not wait for it to do so. 10 | 11 | Normally, `AsyncContextThread` is used by windows services, but it may be used by other applications that need an independent thread with an `AsyncContext`. 12 | 13 | ## Console Example Using AsyncContext 14 | 15 | When using `AsyncContext`, you normally just call the static `Run` method, as such: 16 | 17 | ```C# 18 | class Program 19 | { 20 | static async Task AsyncMain() 21 | { 22 | .. 23 | } 24 | 25 | static int Main(string[] args) 26 | { 27 | return AsyncContext.Run(AsyncMain); 28 | } 29 | } 30 | ``` 31 | 32 | The `Run` method will return when all asynchronous operations have been completed. Any exceptions will be unwrapped and propagated. 33 | 34 | ## AsyncContextThread 35 | 36 | Unlike `AsyncContext`, `AsyncContextThread` provides properties that can be used to schedule tasks on that thread. 37 | 38 | The `Context` property returns the `AsyncContext` being run. The `Factory` property returns a `TaskFactory` which can be used to queue work to the thread. 39 | 40 | ## Platform Support 41 | 42 | This type is available on all platforms, but may not work due to security restrictions. This is particularly a problem on Silverlight and Windows Phone platforms. 43 | -------------------------------------------------------------------------------- /doc/AsyncCountdownEvent.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready almost-equivalent of [CountdownEvent](https://docs.microsoft.com/en-us/dotnet/api/system.threading.countdownevent), similar to Stephen Toub's [AsyncCountdownEvent](https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-3-asynccountdownevent/). It's only an *almost* equivalent because the `AsyncCountdownEvent` does not allow itself to be reset. 4 | 5 | An `AsyncCountdownEvent` starts out **unset** and becomes **set** only once, when its **count** reaches zero. Its current count can be manipulated by any other tasks up until the time it reaches zero. When the count reaches zero, all waiting tasks are released. 6 | 7 | The task returned from `WaitAsync` will enter the `Completed` state when the `AsyncCountdownEvent` has counted down to zero and enters the **set** state. 8 | -------------------------------------------------------------------------------- /doc/AsyncLazy.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | The `AsyncLazy` type enables [asynchronous lazy initialization](https://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html), similar to [Stephen Toub's AsyncLazy](https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/). 4 | 5 | An `AsyncLazy` instance is constructed with a factory method. When the `AsyncLazy` instance is `await`ed or its `Start` method is called, the factory method starts on a thread pool thread. The factory method is only executed once. Once the factory method has completed, all future `await`s on that instance complete immediately. 6 | -------------------------------------------------------------------------------- /doc/AsyncLock.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready almost-equivalent of the `lock` keyword or the [`Mutex` type](https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex), similar to Stephen Toub's [AsyncLock](https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-6-asynclock/). It's only _almost_ equivalent because the `lock` keyword permits reentrancy, which is not currently possible to do with an `async`-ready lock. 4 | 5 | An `AsyncLock` is either taken or not. The lock can be asynchronously acquired by calling `LockAsync`, and it is released by disposing the result of that task. `AsyncLock` taken an optional `CancellationToken`, which can be used to cancel the acquiring of the lock. 6 | 7 | The task returned from `LockAsync` will enter the `Completed` state when it has acquired the `AsyncLock`. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the `AsyncLock` is not taken by that task. 8 | 9 | ## Example Usage 10 | 11 | The vast majority of use cases are to just replace a `lock` statement. That is, with the original code looking like this: 12 | 13 | ```C# 14 | private readonly object _mutex = new object(); 15 | public void DoStuff() 16 | { 17 | lock (_mutex) 18 | { 19 | Thread.Sleep(TimeSpan.FromSeconds(1)); 20 | } 21 | } 22 | ``` 23 | 24 | If we want to replace the blocking operation `Thread.Sleep` with an asynchronous equivalent, it's not directly possible because of the `lock` block. We cannot `await` inside of a `lock`. 25 | 26 | So, we use the `async`-compatible `AsyncLock` instead: 27 | 28 | ```C# 29 | private readonly AsyncLock _mutex = new AsyncLock(); 30 | public async Task DoStuffAsync() 31 | { 32 | using (await _mutex.LockAsync()) 33 | { 34 | await Task.Delay(TimeSpan.FromSeconds(1)); 35 | } 36 | } 37 | ``` 38 | 39 | ## Advanced Usage 40 | 41 | `AsyncLock` also supports synchronous locking with the `Lock` method. 42 | 43 | You can call `Lock` or `LockAsync` [with an already-cancelled `CancellationToken`](Cancellation.md) to attempt to acquire the `AsyncLock` immediately without actually entering the wait queue. 44 | 45 | ## Really Advanced Usage 46 | 47 | The `AsyncLock` constructor can take an async wait queue; pass a custom wait queue to specify your own queueing logic. 48 | -------------------------------------------------------------------------------- /doc/AsyncManualResetEvent.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready equivalent of [ManualResetEvent](https://docs.microsoft.com/en-us/dotnet/api/system.threading.manualresetevent), similar to Stephen Toub's [AsyncManualResetEvent](https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/). 4 | 5 | Like other "events", an `AsyncManualResetEvent` is either **set** or **unset** at any time. An `AsyncManualResetEvent` can be changed from **unset** to **set** by calling its `Set` method, and it can be changed from **set** to **unset** by calling its `Reset` method. 6 | 7 | When an `AsyncManualResetEvent` is in the **set** state, it will satisfy all waiters. Calling `Set` or `Reset` when the `AsyncManualResetEvent` is already in that state is a noop. 8 | 9 | The task returned from `WaitAsync` will enter the `Completed` state when the `AsyncManualResetEvent` is in the **set** state. 10 | 11 | ## Advanced Usage 12 | 13 | `AsyncManualResetEvent` also supports synchronous waiting with the `Wait` method. 14 | 15 | You can call `Wait` with an [already-cancelled `CancellationToken`](Cancellation.md) to test whether the `AsyncManualResetEvent` is in the **set** state. 16 | -------------------------------------------------------------------------------- /doc/AsyncMonitor.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready almost-equivalent of [Monitor](https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor). It's only _almost_ equivalent because the `Monitor` type permits reentrancy, which is not currently possible to do with an `async`-ready lock. 4 | 5 | An `AsyncMonitor` is an [AsyncLock](AsyncLock.md) with a single associated [AsyncConditionVariable](AsyncConditionVariable.md). It is either entered or not. The `AsyncMonitor` can be asynchronously entered by calling `EnterAsync`, and you can leave it by disposing the result of that task. 6 | 7 | While in the monitor, a task may decide to wait for a signal by calling `WaitAsync`. While waiting, it temporarily leaves the monitor until it receives a signal and re-enters the monitor. 8 | 9 | While in the monitor, a signalling task may choose to release only a single waiter (`Pulse`) or all waiters (`PulseAll`). If there are no waiters, the notification is "lost"; it is not remembered by the `AsyncMonitor`. 10 | 11 | The task returned from `EnterAsync` will enter the `Completed` state when it has entered the monitor. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the monitor is not entered by that task. 12 | 13 | The task returned from `WaitAsync` will enter the `Completed` state when it receives a signal and re-enters the monitor. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the task will wait to enter the `Canceled` state until it re-enters the monitor. 14 | 15 | Remember that from the time `WaitAsync` is called to the time when its returned task completes, the calling task has _left_ the monitor. 16 | 17 | Note that the correct logic for waiting on monitor signals is to wait in a loop until the required condition is true. This is necessary because other tasks may execute between the signal and the completion of the wait. 18 | 19 | ## Advanced Usage 20 | 21 | You can call `EnterAsync` with an [already-cancelled `CancellationToken`](Cancellation.md) to attempt to enter the monitor immediately without actually entering the wait queue. 22 | -------------------------------------------------------------------------------- /doc/AsyncProducerConsumerQueue.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | An `AsyncProducerConsumerQueue` is a queue of items that provides `async`-compatible *Enqueue* and *Dequeue* operations. [AsyncCollection](AsyncCollection.md) is more flexible than this type, but it is also more complex. 4 | -------------------------------------------------------------------------------- /doc/AsyncReaderWriterLock.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready almost-equivalent of the the [ReaderWriterLockSlim type](https://docs.microsoft.com/en-us/dotnet/api/system.threading.readerwriterlockslim), similar to Stephen Toub's [AsyncReaderWriterLock](https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/). It's only _almost_ equivalent because the `ReaderWriterLockSlim` supports upgradeable locks and can be constructed in a way which allows reentrancy, and this is not currently possible to do with an `async`-ready lock. 4 | 5 | There are two types of locks that can be taken on an `AsyncReaderWriterLock`: 6 | * Write locks, which are fully exclusive. They do not allow other locks of any kind. 7 | * Read locks, which permit other read locks but exclude write locks. 8 | 9 | Write and read locks may be asynchronously acquired by calling `WriterLockAsync` or `ReaderLockAsync`. These locks are released by disposing the result of the returned task. 10 | 11 | The tasks returned from `WriterLockAsync` and `ReaderLockAsync` will enter the `Completed` state when they have acquired the `AsyncReaderWriterLock`. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the `AsyncReaderWriterLock` is not taken by that task. 12 | 13 | ## Advanced Usage 14 | 15 | You can call `WriterLockAsync` and `ReaderLockAsync` with an [already-cancelled `CancellationToken`](Cancellation.md) to attempt to acquire the `AsyncReaderWriterLock` immediately without actually entering the wait queue. 16 | -------------------------------------------------------------------------------- /doc/AsyncSemaphore.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This is the `async`-ready equivalent of [Semaphore](https://docs.microsoft.com/en-us/dotnet/api/system.threading.semaphore), similar to Stephen Toub's [AsyncSempahore](https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-5-asyncsemaphore/). Alternatively, you can use the [SemaphoreSlim](https://docs.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim) class, which is `async`-ready on modern platforms. 4 | 5 | An `AsyncSemaphore` keeps a count, which is the number of open "slots" it has available to satisfy waiters. Any thread may increase the number of slots available by calling `Release`. 6 | 7 | The task returned from `WaitAsync` will enter the `Completed` state when the `AsyncSemaphore` has given it a slot. That same task will enter the `Canceled` state if the `CancellationToken` is signaled before the wait is satisfied; in that case, the `AsyncSemaphore` does not lose a slot. 8 | 9 | ## Advanced Usage 10 | 11 | You can call `WaitAsync` with an [already-cancelled `CancellationToken`](Cancellation.md) to attempt to acquire a slot from the `AsyncSemaphore` immediately without actually entering the wait queue. 12 | -------------------------------------------------------------------------------- /doc/Cancellation.md: -------------------------------------------------------------------------------- 1 | # Cancellation behavior in AsyncEx 2 | 3 | Most of the asynchronous coordination primitive APIs take an optional `CancellationToken`. There are four different types of "waiting" that can be done. 4 | 5 | Each of the examples here use `AsyncSemaphore.WaitAsync`. 6 | 7 | ## Unconditional wait 8 | 9 | An unconditional wait is the most commonly-used type of wait: the code knows it needs to acquire the semaphore and it will wait however long it takes until the semaphore is available. 10 | 11 | The unconditional wait uses the overload without a `CancellationToken`: `WaitAsync()`. You can also pass `CancellationToken.None`, which has the same effect: `WaitAsync(CancellationToken.None)`. 12 | 13 | ## Cancelable wait 14 | 15 | A cancelable wait is where some event can interrupt the wait if it determines that the code doesn’t need that semaphore anymore. 16 | 17 | To do a cancelable wait, pass in the `CancellationToken` that is used to cancel the wait. If a wait is canceled, the task returned from `WaitAsync` is canceled, and the semaphore is *not* taken. 18 | 19 | ## Atomic wait 20 | 21 | An atomic wait is where the code will immediately (synchronously) acquire the semaphore if it is available. 22 | 23 | To do an atomic wait, pass in a cancellation token that is already canceled. In this case, `WaitAsync` will always return synchronously: a successfully completed task if the semaphore is available (and taken), otherwise a canceled task (and the semaphore is not taken). 24 | 25 | Note: atomic waits should be extremely rare! Carefully review your design if you are considering them. 26 | 27 | ## Timed wait 28 | 29 | A timed wait is a special case of a cancelable wait. 30 | 31 | To do a timed wait, create a cancellation token to cancel the wait (e.g., using [CancellationTokenSource](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.-ctor#System_Threading_CancellationTokenSource__ctor_System_TimeSpan_)) and pass that token to `WaitAsync`. When the timer expires, the wait is canceled. 32 | 33 | Note: timed waits should be extremely, *extremely* rare! Strongly reconsider your design if you are considering them. 34 | -------------------------------------------------------------------------------- /doc/ExceptionHelpers.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | There is one static method on the `ExceptionHelpers` type: `PrepareForRethrow`. 4 | 5 | The purpose of this method is to preserve the original stack trace of the exception so that it is not overwritten when the exception is rethrown. 6 | 7 | The return value of this method should always be thrown immediately; that is, every call to this method should look like this: 8 | 9 | ```C# 10 | Exception ex = ...; // get saved exception 11 | throw ExceptionHelpers.PrepareForRethrow(ex); 12 | ``` 13 | 14 | `PrepareForRethrow` uses [ExceptionDispatchInfo](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.exceptionservices.exceptiondispatchinfo). 15 | -------------------------------------------------------------------------------- /doc/Home.md: -------------------------------------------------------------------------------- 1 | AsyncEx is a library designed to assist with programming when using the `async` and `await` keywords. 2 | 3 | ## Asynchronous Coordination Primitives 4 | 5 | Most people just need [AsyncLock](AsyncLock.md), but AsyncEx also includes [AsyncManualResetEvent](AsyncManualResetEvent.md), [AsyncAutoResetEvent](AsyncAutoResetEvent.md), [AsyncConditionVariable](AsyncConditionVariable.md), [AsyncMonitor](AsyncMonitor.md), [AsyncSemaphore](AsyncSemaphore.md), [AsyncCountdownEvent](AsyncCountdownEvent.md), and [AsyncReaderWriterLock](AsyncReaderWriterLock.md). 6 | 7 | ## Asynchronous and Concurrent Collections 8 | 9 | [AsyncProducerConsumerQueue](AsyncProducerConsumerQueue.md) is an `async`-ready producer/consumer queue that works on all platforms. 10 | 11 | [AsyncCollection](AsyncCollection.md) is an `async`-compatible wrapper around `IProducerConsumerCollection`, in the same way that [BlockingCollection](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1) is a blocking wrapper around `IProducerConsumerCollection`. 12 | 13 | ## AsyncLazy 14 | 15 | The [AsyncLazy](AsyncLazy.md) class provides support for [asynchronous lazy initialization](https://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html). 16 | 17 | ## AsyncContext 18 | 19 | The [AsyncContext](AsyncContext.md) class and the related `AsyncContextThread` class provide contexts for asynchronous operations for situations where such a context is lacking (i.e., Console applications and Win32 services). 20 | 21 | ## Interop 22 | 23 | The [ApmAsyncFactory](ApmAsyncFactory.md) classes interoperate between Task-based Asynchronous Programming (`async`) and the Asynchronous Programming Model (`IAsyncResult`). 24 | 25 | ## Utility Types and Methods 26 | 27 | You can use [OrderByCompletion](TaskExtensions.md) to order tasks by when they complete. 28 | 29 | ## Miscellaneous 30 | 31 | [TaskConstants](TaskConstants.md) provides static constant `Task` values. 32 | 33 | ## Extension Methods 34 | 35 | [TaskCompletionSourceExtensions](TaskCompletionSourceExtensions.md) has several extension methods for `TaskCompletionSource`, including propagating results from a completed `Task`. 36 | 37 | [TaskFactoryExtensions](TaskFactoryExtensions.md) adds `Run` overloads to task factories. 38 | 39 | ## Low-Level Building Blocks 40 | 41 | [ExceptionHelpers.PrepareForRethrow](ExceptionHelpers.md) will preserve the stack trace when rethrowing any exception. 42 | -------------------------------------------------------------------------------- /doc/TaskCompletionSourceExtensions.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | `TaskCompletionSourceExtensions` provides extension methods for the `TaskCompletionSource` type. 4 | 5 | To forward the result of one `Task` to another, call `TryCompleteFromCompletedTask`. 6 | -------------------------------------------------------------------------------- /doc/TaskConstants.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Task constants provide pre-constructed constant task values. These are effective when using the [async fast path](https://blogs.msdn.microsoft.com/lucian/2011/04/15/async-ctp-refresh-design-changes/). 4 | -------------------------------------------------------------------------------- /doc/TaskExtensions.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | `TaskExtensions` provides one method `OrderByCompletion` which orders a sequence of Tasks by when they complete. The approach taken by AsyncEx is a combination of [Jon Skeet's approach](https://codeblog.jonskeet.uk/2012/01/16/eduasync-part-19-ordering-by-completion-ahead-of-time/) and [Stephen Toub's approach](https://blogs.msdn.microsoft.com/pfxteam/2012/08/02/processing-tasks-as-they-complete/). 4 | 5 | `TaskExtensions` in the `Nito.AsyncEx.Synchronous` namespace provides a handful of extension methods that enable synchronous blocking of a `Task` without wrapping its exception in an `AggregateException`, or without observing the `Task` exception at all. These are advanced methods that should probably not be used, since you [run the risk of a deadlock](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). 6 | -------------------------------------------------------------------------------- /doc/TaskFactoryExtensions.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | `TaskFactoryExtensions` provides a series of `Run` overloads for scheduling `async` as well as synchronous delegates. 4 | -------------------------------------------------------------------------------- /doc/design.md: -------------------------------------------------------------------------------- 1 | # Design Guidelines 2 | 3 | - Do not name methods starting with a `Try` prefix. Particularly in the asynchronous world, ["Try" is ambiguous](https://github.com/StephenCleary/AsyncEx/issues/141#issuecomment-409287646); does it mean "attempt this synchronously" or "attempt this without exceptions"? 4 | - `TaskCompletionSourceExtensions.TryCompleteFromCompletedTask` is an exception to this rule; it uses the `Try` prefix to match the existing `TaskCompletionSource` API. 5 | - Try to structure APIs to guide users into success rather than failure. 6 | - E.g., coordination primitives should avoid exposing their current state via properties because those properties would encourage code with race conditions. 7 | - Do not use strong naming. Currently, adding strong naming creates too much of a maintenance burden; if Microsoft releases better strongname tooling, then this guideline can be reconsidered. 8 | 9 | # Additional guidelines for AsyncEx.Coordination 10 | 11 | - The API for asynchronous coordination primitives should mimic that of their synchronous counterparts, with the addition of asynchronous APIs and `CancellationToken` support. 12 | - In particular, if you find yourself wanting to add an API to a coordination primitive, you're almost certainly using the wrong primitive. 13 | - `AutoResetEvent.Reset` is [an exception to this rule](https://github.com/StephenCleary/AsyncEx/issues/27#issuecomment-133921579). 14 | - Keep all synchronization primitives consistent. If you're adding something to one type, consider **all** other primitives and add it to all of them at the same time. 15 | - [Do not use explicit timeouts (`int` or `TimeSpan` parameters)](https://github.com/StephenCleary/AsyncEx/issues/46#issuecomment-187685580). These are only present on the synchronous primitives because they can be passed down to the Win32 API layer; in the asynchronous primitive world, use `CancellationToken` instead. If you really want overloads with timeout parameters, they are easy to add as extension methods in your own code. 16 | - When unit testing, do not have timing-dependent tests. 17 | -------------------------------------------------------------------------------- /doc/upgrade.md: -------------------------------------------------------------------------------- 1 | # Upgrading from v4 to v5 2 | 3 | ## General 4 | 5 | All `EnlightenmentVerification` has been removed; the new library no longer requires enlightenment. 6 | 7 | ## Coordination Primitives 8 | 9 | ### AsyncLazy 10 | 11 | The constructor for `AsyncLazy` that takes a `Func` has been removed; this will cause a compilation error on upgrade. Replace any such calls with an explicit wrapping in `Task.Run` and pass `AsyncLazyFlags.ExecuteOnCallingThread`: 12 | 13 | // Old code (where T.Create returns a T, not a Task): 14 | new AsyncLazy(() => T.Create()); 15 | 16 | // New code: 17 | new AsyncLazy(() => Task.Run(() => T.Create()), AsyncLazyFlags.ExecuteOnCallingThread); 18 | 19 | This is an unusual use case for `AsyncLazy`; most code uses the constructor that takes a `Func>`, and will not need to change. 20 | 21 | ### AsyncReaderWriterLock 22 | 23 | Upgradeable reader locks no longer exist. The `UpgradeableReaderLock` and `UpgradeableReaderLockAsync` methods have been removed, along with the nested `AsyncReaderWriterLock.UpgradeableReaderKey` type. 24 | 25 | ### AsyncProducerConsumerQueue 26 | 27 | `AsyncProducerConsumerQueue` has been simplified; it is no longer disposable, and the `Try` methods have been removed (they caused a good deal of confusion due to the multiple meanings of the `Try` prefix in .NET coding patterns). This includes `TryDequeue`, `TryDequeueAsync`, `TryEnqueue`, and `TryEnqueueAsync`. 28 | 29 | All multi-queue operations have been removed (`DequeueFromAny`, `DequeueFromAnyAsync`, `TryDequeueFromAny`, `TryDequeueFromAnyAsync`, `EnqueueToAny`, `EnqueueToAnyAsync`, `TryEnqueueToAny`, and `TryEnqueueToAnyAsync`). 30 | 31 | In addition, the nested `AsyncProducerConsumerQueue.DequeueResult` type is no longer necessary, and is gone. 32 | 33 | The obsolete `CompleteAddingAsync` has also been removed; use `CompleteAdding` instead. 34 | 35 | ### AsyncCountdownEvent 36 | 37 | The semantics for `AsyncCountdownEvent` have been modified; its count can now be negative, and the event is only signalled when the count is `0`. Integer overflow or underflow now always cause exceptions, so the `TryAddCount` and `TrySignal` methods have been removed. 38 | 39 | ### AsyncBarrier 40 | 41 | `AsyncBarrier` has been removed. Replace uses of `AsyncBarrier` with appropriate explicit synchronization primitives (e.g., `AsyncManualResetEvent`). 42 | 43 | ### Others 44 | 45 | The `IAsyncWaitQueue` infrastructure has been considerably simplified. 46 | 47 | The following types have no backwards-incompatible API changes: `AsyncAutoResetEvent`, `AsyncLock`, `AsyncManualResetEvent`, `AsyncMonitor`, `AsyncSemaphore`, `PauseToken`, `PauseTokenSource`. 48 | 49 | ## Context 50 | 51 | `AsyncContext` has not changed. 52 | 53 | `AsyncContextThread` can no longer be an STA thread. The STA constructor for `AsyncContextThread` has been removed. 54 | 55 | ## Tasks 56 | 57 | `TaskConstants.Never` and `TaskConstants.Never` have been removed, as they can easily cause memory leaks if misused. 58 | 59 | The non-generic `TaskCompletionSource` has been removed. Use `TaskCompletionSource` instead. 60 | 61 | The `WithBackgroundContinuations` extension methods for `TaskCompletionSource` have been removed (`TrySetCanceledWithBackgroundContinuations`, `TrySetExceptionWithBackgroundContinuations`, `TrySetResultWithBackgroundContinuations`); instead, use the `TaskCompletionSourceExtensions.CreateAsyncTaskSource` to create a TCS that always uses asynchronous continuations. 62 | 63 | `TaskCompletionSource.TryCompleteFromEventArgs` has been temporarily removed; it will be added back in a future version as part of `Nito.AsyncEx.Interop.Eap`. 64 | 65 | All other `Task`/`Task`, `TaskFactory`, and `TaskCompletionSource` extensions have not changed. 66 | 67 | ## SynchronizationContext 68 | 69 | The nested type `SynchronizationContextHelpers.SynchronizationContextSwitcher` is now a top-level type `SynchronizationContextSwitcher`. 70 | 71 | `SynchronizationContextHelpers.CurrentOrDefault` has been removed; use `SynchronizationContext.Current ?? new SynchronizationContext()` instead. 72 | 73 | ## Interop 74 | 75 | `FromApm` has been removed from `AsyncFactory`/`AsyncFactory`; use `TaskFactory.FromAsync` instead. 76 | 77 | `ToBegin` and `ToEnd` on `AsyncFactory`/`AsyncFactory` have been moved to `ApmAsyncFactory` in the `Nito.AsyncEx.Interop` namespace. 78 | 79 | `AsyncFactory.FromWaitHandle` has been moved to `WaitHandleAsyncFactory` in the `Nito.AsyncEx.Interop` namespace. 80 | 81 | `AsyncFactory.FromEvent` has been replaced with the more strongly-typed `EventAsyncFactory` in the `Nito.AsyncEx.Interop` namespace. 82 | 83 | ## Cancellation 84 | 85 | `NormalizedCancellationToken` has not changed. 86 | 87 | The nested type `CancellationTokenExtensions.CancellationTokenTaskSource` is now a top-level generic type `CancellationTokenTaskSource`. 88 | 89 | The obsolete `CancellationToken.AsTask` extension method has been removed; use `CancellationTokenTaskSource` instead. 90 | 91 | The `CancellationToken.ToCancellationTokenTaskSource` extension method has been removed; use the new `Task` extensions (`WaitAsync`, `WhenAny`, `WhenAll`) or the `CancellationTokenTaskSource` constructor instead. 92 | 93 | `CancellationTokenHelpers` has been split up. `Timeout` and `Normalize` have moved to `NormalizedCancellationToken`. `CancellationTokenHelpers.None` should change to `CancellationToken.None`, and `CancellationTokenHelpers.Canceled` should change to `new CancellationToken(true)`. `CancellationTokenHelpers.FromTask` has been removed and has no replacement; let me know if you want this functionality. 94 | 95 | ## OOP 96 | 97 | The API for `DeferralManager` has changed to be more easy to use. It now follows the pattern of having event arg types implement `IDeferralSource` by forwarding to `DeferralManager.DeferralSource`. This prevents the `DeferralManager` from being exposed to the event args type. Also, `SignalAndWaitAsync` has been renamed to `WaitForDeferralsAsync`. 98 | 99 | ## MVVM 100 | 101 | All MVVM types have been moved to the `Nito.Mvvm.Async` library. It is currently not included in `Nito.AsyncEx` since the MVVM APIs are still too much in flux. Eventually, `Nito.Mvvm.Async` will probably be rolled back in to `Nito.AsyncEx`, but for 5.0.0 it will be a separate install. 102 | 103 | `PropertyProgress` has been temporarily removed. It will be replaced with a more powerful type in the future. 104 | 105 | `ProducerProgress` has been permanently removed. 106 | 107 | `INotifyTaskCompletion` and `NotifyTaskCompletion` have been replaced with `NotifyTask`. `INotifyTaskCompletion` has been replaced with `NotifyTask`. 108 | 109 | ## Misc 110 | 111 | `ExceptionHelpers` has not changed. 112 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | true 15 | latest 16 | enable 17 | true 18 | 19 | 20 | true 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | https://github.com/$(GITHUB_REPOSITORY) 32 | MIT 33 | 34 | 35 | 36 | 37 | icon.png 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | true 46 | true 47 | true 48 | snupkg 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | dev 62 | 63 | 64 | 65 | 66 | 67 | 68 | 72 | 73 | <_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/> 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | false 11 | false 12 | NU5128 13 | 14 | 15 | 16 | true 17 | 18 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Context/AsyncContext.SynchronizationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Nito.AsyncEx.Synchronous; 4 | 5 | namespace Nito.AsyncEx 6 | { 7 | public sealed partial class AsyncContext 8 | { 9 | /// 10 | /// The implementation used by . 11 | /// 12 | private sealed class AsyncContextSynchronizationContext : SynchronizationContext 13 | { 14 | /// 15 | /// The async context. 16 | /// 17 | private readonly AsyncContext _context; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The async context. 23 | public AsyncContextSynchronizationContext(AsyncContext context) 24 | { 25 | _context = context; 26 | } 27 | 28 | /// 29 | /// Gets the async context. 30 | /// 31 | public AsyncContext Context 32 | { 33 | get 34 | { 35 | return _context; 36 | } 37 | } 38 | 39 | /// 40 | /// Dispatches an asynchronous message to the async context. If all tasks have been completed and the outstanding asynchronous operation count is zero, then this method has undefined behavior. 41 | /// 42 | /// The delegate to call. May not be null. 43 | /// The object passed to the delegate. 44 | public override void Post(SendOrPostCallback d, object state) 45 | { 46 | _context.Enqueue(_context._taskFactory.Run(() => d(state)), true); 47 | } 48 | 49 | /// 50 | /// Dispatches an asynchronous message to the async context, and waits for it to complete. 51 | /// 52 | /// The delegate to call. May not be null. 53 | /// The object passed to the delegate. 54 | public override void Send(SendOrPostCallback d, object state) 55 | { 56 | if (AsyncContext.Current == _context) 57 | { 58 | d(state); 59 | } 60 | else 61 | { 62 | var task = _context._taskFactory.Run(() => d(state)); 63 | task.WaitAndUnwrapException(); 64 | } 65 | } 66 | 67 | /// 68 | /// Responds to the notification that an operation has started by incrementing the outstanding asynchronous operation count. 69 | /// 70 | public override void OperationStarted() 71 | { 72 | _context.OperationStarted(); 73 | } 74 | 75 | /// 76 | /// Responds to the notification that an operation has completed by decrementing the outstanding asynchronous operation count. 77 | /// 78 | public override void OperationCompleted() 79 | { 80 | _context.OperationCompleted(); 81 | } 82 | 83 | /// 84 | /// Creates a copy of the synchronization context. 85 | /// 86 | /// A new object. 87 | public override SynchronizationContext CreateCopy() 88 | { 89 | return new AsyncContextSynchronizationContext(_context); 90 | } 91 | 92 | /// 93 | /// Returns a hash code for this instance. 94 | /// 95 | /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 96 | public override int GetHashCode() 97 | { 98 | return _context.GetHashCode(); 99 | } 100 | 101 | /// 102 | /// Determines whether the specified is equal to this instance. It is considered equal if it refers to the same underlying async context as this instance. 103 | /// 104 | /// The to compare with this instance. 105 | /// true if the specified is equal to this instance; otherwise, false. 106 | public override bool Equals(object obj) 107 | { 108 | var other = obj as AsyncContextSynchronizationContext; 109 | if (other == null) 110 | return false; 111 | return (_context == other._context); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Context/AsyncContext.TaskQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace Nito.AsyncEx 7 | { 8 | public sealed partial class AsyncContext 9 | { 10 | /// 11 | /// A blocking queue. 12 | /// 13 | private sealed class TaskQueue : IDisposable 14 | { 15 | /// 16 | /// The underlying blocking collection. 17 | /// 18 | private readonly BlockingCollection> _queue; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public TaskQueue() 24 | { 25 | _queue = new BlockingCollection>(); 26 | } 27 | 28 | /// 29 | /// Gets a blocking enumerable that removes items from the queue. This enumerable only completes after has been called. 30 | /// 31 | /// A blocking enumerable that removes items from the queue. 32 | public IEnumerable> GetConsumingEnumerable() 33 | { 34 | return _queue.GetConsumingEnumerable(); 35 | } 36 | 37 | /// 38 | /// Generates an enumerable of instances currently queued to the scheduler waiting to be executed. 39 | /// 40 | /// An enumerable that allows traversal of tasks currently queued to this scheduler. 41 | [System.Diagnostics.DebuggerNonUserCode] 42 | internal IEnumerable GetScheduledTasks() 43 | { 44 | foreach (var item in _queue) 45 | yield return item.Item1; 46 | } 47 | 48 | /// 49 | /// Attempts to add the item to the queue. If the queue has been marked as complete for adding, this method returns false. 50 | /// 51 | /// The item to enqueue. 52 | /// A value indicating whether exceptions on this task should be propagated out of the main loop. 53 | public bool TryAdd(Task item, bool propagateExceptions) 54 | { 55 | try 56 | { 57 | return _queue.TryAdd(Tuple.Create(item, propagateExceptions)); 58 | } 59 | catch (InvalidOperationException) 60 | { 61 | // vexing exception 62 | return false; 63 | } 64 | } 65 | 66 | /// 67 | /// Marks the queue as complete for adding, allowing the enumerator returned from to eventually complete. This method may be called several times. 68 | /// 69 | public void CompleteAdding() 70 | { 71 | _queue.CompleteAdding(); 72 | } 73 | 74 | /// 75 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 76 | /// 77 | public void Dispose() 78 | { 79 | _queue.Dispose(); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Context/AsyncContext.TaskScheduler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace Nito.AsyncEx 5 | { 6 | public sealed partial class AsyncContext 7 | { 8 | /// 9 | /// A task scheduler which schedules tasks to an async context. 10 | /// 11 | private sealed class AsyncContextTaskScheduler : TaskScheduler 12 | { 13 | /// 14 | /// The async context for this task scheduler. 15 | /// 16 | private readonly AsyncContext _context; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// The async context for this task scheduler. May not be null. 22 | public AsyncContextTaskScheduler(AsyncContext context) 23 | { 24 | _context = context; 25 | } 26 | 27 | /// 28 | /// Generates an enumerable of instances currently queued to the scheduler waiting to be executed. 29 | /// 30 | /// An enumerable that allows traversal of tasks currently queued to this scheduler. 31 | [System.Diagnostics.DebuggerNonUserCode] 32 | protected override IEnumerable GetScheduledTasks() 33 | { 34 | return _context._queue.GetScheduledTasks(); 35 | } 36 | 37 | /// 38 | /// Queues a to the scheduler. If all tasks have been completed and the outstanding asynchronous operation count is zero, then this method has undefined behavior. 39 | /// 40 | /// The to be queued. 41 | protected override void QueueTask(Task task) 42 | { 43 | _context.Enqueue(task, false); 44 | } 45 | 46 | /// 47 | /// Determines whether the provided can be executed synchronously in this call, and if it can, executes it. 48 | /// 49 | /// The to be executed. 50 | /// A Boolean denoting whether or not task has previously been queued. If this parameter is True, then the task may have been previously queued (scheduled); if False, then the task is known not to have been queued, and this call is being made in order to execute the task inline without queuing it. 51 | /// A Boolean value indicating whether the task was executed inline. 52 | /// The was already executed. 53 | protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 54 | { 55 | return (AsyncContext.Current == _context) && TryExecuteTask(task); 56 | } 57 | 58 | /// 59 | /// Indicates the maximum concurrency level this is able to support. 60 | /// 61 | public override int MaximumConcurrencyLevel 62 | { 63 | get { return 1; } 64 | } 65 | 66 | /// 67 | /// Exposes the base method. 68 | /// 69 | /// The task to attempt to execute. 70 | public void DoTryExecuteTask(Task task) 71 | { 72 | TryExecuteTask(task); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Context/AsyncContextThread.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Nito.AsyncEx.Synchronous; 6 | 7 | namespace Nito.AsyncEx 8 | { 9 | /// 10 | /// A thread that executes actions within an . 11 | /// 12 | [DebuggerTypeProxy(typeof(DebugView))] 13 | public sealed class AsyncContextThread : Disposables.SingleDisposable 14 | { 15 | /// 16 | /// The child thread. 17 | /// 18 | private readonly Task _thread; 19 | 20 | /// 21 | /// Creates a new and increments its operation count. 22 | /// 23 | private static AsyncContext CreateAsyncContext() 24 | { 25 | var result = new AsyncContext(); 26 | result.SynchronizationContext.OperationStarted(); 27 | return result; 28 | } 29 | 30 | /// 31 | /// Initializes a new instance of the class, creating a child thread waiting for commands. 32 | /// 33 | /// The context for this thread. 34 | private AsyncContextThread(AsyncContext context) 35 | : base(context) 36 | { 37 | Context = context; 38 | _thread = Task.Factory.StartNew(Execute, CancellationToken.None, TaskCreationOptions.LongRunning | TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); 39 | } 40 | 41 | /// 42 | /// Initializes a new instance of the class, creating a child thread waiting for commands. 43 | /// 44 | public AsyncContextThread() 45 | : this(CreateAsyncContext()) 46 | { 47 | } 48 | 49 | /// 50 | /// Gets the executed by this thread. 51 | /// 52 | public AsyncContext Context { get; } 53 | 54 | private void Execute() 55 | { 56 | using (Context) 57 | { 58 | Context.Execute(); 59 | } 60 | } 61 | 62 | /// 63 | /// Permits the thread to exit, if we have not already done so. 64 | /// 65 | private void AllowThreadToExit() 66 | { 67 | Context.SynchronizationContext.OperationCompleted(); 68 | } 69 | 70 | /// 71 | /// Requests the thread to exit and returns a task representing the exit of the thread. The thread will exit when all outstanding asynchronous operations complete. 72 | /// 73 | public Task JoinAsync() 74 | { 75 | Dispose(); 76 | return _thread; 77 | } 78 | 79 | /// 80 | /// Requests the thread to exit and blocks until the thread exits. The thread will exit when all outstanding asynchronous operations complete. 81 | /// 82 | public void Join() 83 | { 84 | JoinAsync().WaitAndUnwrapException(); 85 | } 86 | 87 | /// 88 | /// Requests the thread to exit. 89 | /// 90 | protected override void Dispose(AsyncContext context) 91 | { 92 | AllowThreadToExit(); 93 | } 94 | 95 | /// 96 | /// Gets the for this thread, which can be used to schedule work to this thread. 97 | /// 98 | public TaskFactory Factory => Context.Factory; 99 | 100 | [DebuggerNonUserCode] 101 | #pragma warning disable CA1812 // Avoid uninstantiated internal classes 102 | internal sealed class DebugView 103 | #pragma warning restore CA1812 // Avoid uninstantiated internal classes 104 | { 105 | private readonly AsyncContextThread _thread; 106 | 107 | public DebugView(AsyncContextThread thread) 108 | { 109 | _thread = thread; 110 | } 111 | 112 | public AsyncContext Context => _thread.Context; 113 | 114 | public object Thread => _thread._thread; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Context/Nito.AsyncEx.Context.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | A single-threaded async-compatible context. 5 | netstandard1.3;netstandard2.0;net461 6 | $(PackageTags);synchronizationcontext 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Context/SynchronizationContextSwitcher.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: TypeForwardedTo(typeof(Nito.AsyncEx.SynchronizationContextSwitcher))] 4 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Nito.AsyncEx.Synchronous; 6 | 7 | // Original idea by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266923.aspx 8 | 9 | namespace Nito.AsyncEx 10 | { 11 | /// 12 | /// An async-compatible auto-reset event. 13 | /// 14 | [DebuggerDisplay("Id = {Id}, IsSet = {_set}")] 15 | [DebuggerTypeProxy(typeof(DebugView))] 16 | public sealed class AsyncAutoResetEvent 17 | { 18 | /// 19 | /// The queue of TCSs that other tasks are awaiting. 20 | /// 21 | private readonly IAsyncWaitQueue _queue; 22 | 23 | /// 24 | /// The current state of the event. 25 | /// 26 | private bool _set; 27 | 28 | /// 29 | /// The semi-unique identifier for this instance. This is 0 if the id has not yet been created. 30 | /// 31 | private int _id; 32 | 33 | /// 34 | /// The object used for mutual exclusion. 35 | /// 36 | private readonly object _mutex; 37 | 38 | /// 39 | /// Creates an async-compatible auto-reset event. 40 | /// 41 | /// Whether the auto-reset event is initially set or unset. 42 | /// The wait queue used to manage waiters. This may be null to use a default (FIFO) queue. 43 | internal AsyncAutoResetEvent(bool set, IAsyncWaitQueue? queue) 44 | { 45 | _queue = queue ?? new DefaultAsyncWaitQueue(); 46 | _set = set; 47 | _mutex = new object(); 48 | } 49 | 50 | /// 51 | /// Creates an async-compatible auto-reset event. 52 | /// 53 | /// Whether the auto-reset event is initially set or unset. 54 | public AsyncAutoResetEvent(bool set) 55 | : this(set, null) 56 | { 57 | } 58 | 59 | /// 60 | /// Creates an async-compatible auto-reset event that is initially unset. 61 | /// 62 | public AsyncAutoResetEvent() 63 | : this(false, null) 64 | { 65 | } 66 | 67 | /// 68 | /// Gets a semi-unique identifier for this asynchronous auto-reset event. 69 | /// 70 | public int Id 71 | { 72 | get { return IdManager.GetId(ref _id); } 73 | } 74 | 75 | /// 76 | /// Whether this event is currently set. This member is seldom used; code using this member has a high possibility of race conditions. 77 | /// 78 | public bool IsSet 79 | { 80 | get { lock (_mutex) return _set; } 81 | } 82 | 83 | /// 84 | /// Asynchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately, even if the cancellation token is already signalled. If the wait is canceled, then it will not auto-reset this event. 85 | /// 86 | /// The cancellation token used to cancel this wait. 87 | public Task WaitAsync(CancellationToken cancellationToken) 88 | { 89 | Task ret; 90 | lock (_mutex) 91 | { 92 | if (_set) 93 | { 94 | _set = false; 95 | ret = TaskConstants.Completed; 96 | } 97 | else 98 | { 99 | ret = _queue.Enqueue(_mutex, cancellationToken); 100 | } 101 | } 102 | 103 | return ret; 104 | } 105 | 106 | /// 107 | /// Asynchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately. 108 | /// 109 | public Task WaitAsync() 110 | { 111 | return WaitAsync(CancellationToken.None); 112 | } 113 | 114 | /// 115 | /// Synchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately, even if the cancellation token is already signalled. If the wait is canceled, then it will not auto-reset this event. This method may block the calling thread. 116 | /// 117 | /// The cancellation token used to cancel this wait. 118 | public void Wait(CancellationToken cancellationToken) 119 | { 120 | WaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); 121 | } 122 | 123 | /// 124 | /// Synchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately. This method may block the calling thread. 125 | /// 126 | public void Wait() 127 | { 128 | Wait(CancellationToken.None); 129 | } 130 | 131 | 132 | #pragma warning disable CA1200 // Avoid using cref tags with a prefix 133 | /// 134 | /// Sets the event, atomically completing a task returned by . If the event is already set, this method does nothing. 135 | /// 136 | public void Set() 137 | #pragma warning restore CA1200 // Avoid using cref tags with a prefix 138 | { 139 | lock (_mutex) 140 | { 141 | if (_queue.IsEmpty) 142 | _set = true; 143 | else 144 | _queue.Dequeue(); 145 | } 146 | } 147 | 148 | // ReSharper disable UnusedMember.Local 149 | [DebuggerNonUserCode] 150 | private sealed class DebugView 151 | { 152 | private readonly AsyncAutoResetEvent _are; 153 | 154 | public DebugView(AsyncAutoResetEvent are) 155 | { 156 | _are = are; 157 | } 158 | 159 | public int Id { get { return _are.Id; } } 160 | 161 | public bool IsSet { get { return _are._set; } } 162 | 163 | public IAsyncWaitQueue WaitQueue { get { return _are._queue; } } 164 | } 165 | // ReSharper restore UnusedMember.Local 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Nito.AsyncEx.Synchronous; 6 | 7 | namespace Nito.AsyncEx 8 | { 9 | /// 10 | /// An async-compatible condition variable. This type uses Mesa-style semantics (the notifying tasks do not yield). 11 | /// 12 | [DebuggerDisplay("Id = {Id}, AsyncLockId = {_asyncLock.Id}")] 13 | [DebuggerTypeProxy(typeof(DebugView))] 14 | public sealed class AsyncConditionVariable 15 | { 16 | /// 17 | /// The lock associated with this condition variable. 18 | /// 19 | private readonly AsyncLock _asyncLock; 20 | 21 | /// 22 | /// The queue of waiting tasks. 23 | /// 24 | private readonly IAsyncWaitQueue _queue; 25 | 26 | /// 27 | /// The semi-unique identifier for this instance. This is 0 if the id has not yet been created. 28 | /// 29 | private int _id; 30 | 31 | /// 32 | /// The object used for mutual exclusion. 33 | /// 34 | private readonly object _mutex; 35 | 36 | /// 37 | /// Creates an async-compatible condition variable associated with an async-compatible lock. 38 | /// 39 | /// The lock associated with this condition variable. 40 | /// The wait queue used to manage waiters. This may be null to use a default (FIFO) queue. 41 | internal AsyncConditionVariable(AsyncLock asyncLock, IAsyncWaitQueue? queue) 42 | { 43 | _asyncLock = asyncLock; 44 | _queue = queue ?? new DefaultAsyncWaitQueue(); 45 | _mutex = new object(); 46 | } 47 | 48 | /// 49 | /// Creates an async-compatible condition variable associated with an async-compatible lock. 50 | /// 51 | /// The lock associated with this condition variable. 52 | public AsyncConditionVariable(AsyncLock asyncLock) 53 | : this(asyncLock, null) 54 | { 55 | } 56 | 57 | /// 58 | /// Gets a semi-unique identifier for this asynchronous condition variable. 59 | /// 60 | public int Id 61 | { 62 | get { return IdManager.GetId(ref _id); } 63 | } 64 | 65 | /// 66 | /// Sends a signal to a single task waiting on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when this method returns. 67 | /// 68 | public void Notify() 69 | { 70 | lock (_mutex) 71 | { 72 | if (!_queue.IsEmpty) 73 | _queue.Dequeue(); 74 | } 75 | } 76 | 77 | /// 78 | /// Sends a signal to all tasks waiting on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when this method returns. 79 | /// 80 | public void NotifyAll() 81 | { 82 | lock (_mutex) 83 | { 84 | _queue.DequeueAll(); 85 | } 86 | } 87 | 88 | /// 89 | /// Asynchronously waits for a signal on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when this method returns, even if the method is cancelled. 90 | /// 91 | /// The cancellation signal used to cancel this wait. 92 | public Task WaitAsync(CancellationToken cancellationToken) 93 | { 94 | Task task; 95 | lock (_mutex) 96 | { 97 | // Begin waiting for either a signal or cancellation. 98 | task = _queue.Enqueue(_mutex, cancellationToken); 99 | 100 | // Attach to the signal or cancellation. 101 | var ret = WaitAndRetakeLockAsync(task, _asyncLock); 102 | 103 | // Release the lock while we are waiting. 104 | _asyncLock.ReleaseLock(); 105 | 106 | return ret; 107 | } 108 | } 109 | 110 | private static async Task WaitAndRetakeLockAsync(Task task, AsyncLock asyncLock) 111 | { 112 | try 113 | { 114 | await task.ConfigureAwait(false); 115 | } 116 | finally 117 | { 118 | // Re-take the lock. 119 | await asyncLock.LockAsync().ConfigureAwait(false); 120 | } 121 | } 122 | 123 | /// 124 | /// Asynchronously waits for a signal on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when the returned task completes. 125 | /// 126 | public Task WaitAsync() 127 | { 128 | return WaitAsync(CancellationToken.None); 129 | } 130 | 131 | /// 132 | /// Synchronously waits for a signal on this condition variable. This method may block the calling thread. The associated lock MUST be held when calling this method, and it will still be held when this method returns, even if the method is cancelled. 133 | /// 134 | /// The cancellation signal used to cancel this wait. 135 | public void Wait(CancellationToken cancellationToken) 136 | { 137 | WaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); 138 | } 139 | 140 | /// 141 | /// Synchronously waits for a signal on this condition variable. This method may block the calling thread. The associated lock MUST be held when calling this method, and it will still be held when this method returns. 142 | /// 143 | public void Wait() 144 | { 145 | Wait(CancellationToken.None); 146 | } 147 | 148 | // ReSharper disable UnusedMember.Local 149 | [DebuggerNonUserCode] 150 | private sealed class DebugView 151 | { 152 | private readonly AsyncConditionVariable _cv; 153 | 154 | public DebugView(AsyncConditionVariable cv) 155 | { 156 | _cv = cv; 157 | } 158 | 159 | public int Id { get { return _cv.Id; } } 160 | 161 | public AsyncLock AsyncLock { get { return _cv._asyncLock; } } 162 | 163 | public IAsyncWaitQueue WaitQueue { get { return _cv._queue; } } 164 | } 165 | // ReSharper restore UnusedMember.Local 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/AsyncCountdownEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | // Original idea by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266930.aspx 7 | 8 | namespace Nito.AsyncEx 9 | { 10 | /// 11 | /// An async-compatible countdown event. 12 | /// 13 | [DebuggerDisplay("Id = {Id}, CurrentCount = {_count}")] 14 | [DebuggerTypeProxy(typeof(DebugView))] 15 | public sealed class AsyncCountdownEvent 16 | { 17 | /// 18 | /// The underlying manual-reset event. 19 | /// 20 | private readonly AsyncManualResetEvent _mre; 21 | 22 | /// 23 | /// The remaining count on this event. 24 | /// 25 | private long _count; 26 | 27 | /// 28 | /// Creates an async-compatible countdown event. 29 | /// 30 | /// The number of signals this event will need before it becomes set. 31 | public AsyncCountdownEvent(long count) 32 | { 33 | _mre = new AsyncManualResetEvent(count == 0); 34 | _count = count; 35 | } 36 | 37 | /// 38 | /// Gets a semi-unique identifier for this asynchronous countdown event. 39 | /// 40 | public int Id 41 | { 42 | get { return _mre.Id; } 43 | } 44 | 45 | /// 46 | /// Gets the current number of remaining signals before this event becomes set. This member is seldom used; code using this member has a high possibility of race conditions. 47 | /// 48 | public long CurrentCount 49 | { 50 | get 51 | { 52 | lock (_mre) 53 | return _count; 54 | } 55 | } 56 | 57 | /// 58 | /// Asynchronously waits for the count to reach zero. 59 | /// 60 | public Task WaitAsync() 61 | { 62 | return _mre.WaitAsync(); 63 | } 64 | 65 | /// 66 | /// Synchronously waits for the count to reach zero. This method may block the calling thread. 67 | /// 68 | /// The cancellation token used to cancel the wait. If this token is already canceled, this method will first check whether the event is set. 69 | public Task WaitAsync(CancellationToken cancellationToken) 70 | { 71 | return _mre.WaitAsync(cancellationToken); 72 | } 73 | 74 | /// 75 | /// Synchronously waits for the count to reach zero. This method may block the calling thread. 76 | /// 77 | public void Wait() 78 | { 79 | _mre.Wait(); 80 | } 81 | 82 | /// 83 | /// Synchronously waits for the count to reach zero. This method may block the calling thread. 84 | /// 85 | /// The cancellation token used to cancel the wait. If this token is already canceled, this method will first check whether the event is set. 86 | public void Wait(CancellationToken cancellationToken) 87 | { 88 | _mre.Wait(cancellationToken); 89 | } 90 | 91 | /// 92 | /// Attempts to modify the current count by the specified amount. 93 | /// 94 | /// The amount to change the current count. 95 | /// true to add to the current count; false to subtract. 96 | private void ModifyCount(long difference, bool add) 97 | { 98 | if (difference == 0) 99 | return; 100 | lock (_mre) 101 | { 102 | var oldCount = _count; 103 | checked 104 | { 105 | if (add) 106 | _count += difference; 107 | else 108 | _count -= difference; 109 | } 110 | if (oldCount == 0) 111 | { 112 | _mre.Reset(); 113 | } 114 | else if (_count == 0) 115 | { 116 | _mre.Set(); 117 | } 118 | else if ((oldCount < 0 && _count > 0) || (oldCount > 0 && _count < 0)) 119 | { 120 | _mre.Set(); 121 | _mre.Reset(); 122 | } 123 | } 124 | } 125 | 126 | /// 127 | /// Adds the specified value to the current count. 128 | /// 129 | /// The amount to change the current count. 130 | public void AddCount(long addCount) 131 | { 132 | ModifyCount(addCount, true); 133 | } 134 | 135 | /// 136 | /// Adds one to the current count. 137 | /// 138 | public void AddCount() 139 | { 140 | AddCount(1); 141 | } 142 | 143 | /// 144 | /// Subtracts the specified value from the current count. 145 | /// 146 | /// The amount to change the current count. 147 | public void Signal(long signalCount) 148 | { 149 | ModifyCount(signalCount, false); 150 | } 151 | 152 | /// 153 | /// Subtracts one from the current count. 154 | /// 155 | public void Signal() 156 | { 157 | Signal(1); 158 | } 159 | 160 | // ReSharper disable UnusedMember.Local 161 | [DebuggerNonUserCode] 162 | private sealed class DebugView 163 | { 164 | private readonly AsyncCountdownEvent _ce; 165 | 166 | public DebugView(AsyncCountdownEvent ce) 167 | { 168 | _ce = ce; 169 | } 170 | 171 | public int Id { get { return _ce.Id; } } 172 | 173 | public long CurrentCount { get { return _ce.CurrentCount; } } 174 | 175 | public AsyncManualResetEvent AsyncManualResetEvent { get { return _ce._mre; } } 176 | } 177 | // ReSharper restore UnusedMember.Local 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/AsyncManualResetEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Nito.AsyncEx.Synchronous; 5 | 6 | // Original idea by Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx 7 | 8 | namespace Nito.AsyncEx 9 | { 10 | /// 11 | /// An async-compatible manual-reset event. 12 | /// 13 | [DebuggerDisplay("Id = {Id}, IsSet = {GetStateForDebugger}")] 14 | [DebuggerTypeProxy(typeof(DebugView))] 15 | public sealed class AsyncManualResetEvent 16 | { 17 | /// 18 | /// The object used for synchronization. 19 | /// 20 | private readonly object _mutex; 21 | 22 | /// 23 | /// The current state of the event. 24 | /// 25 | private TaskCompletionSource _tcs; 26 | 27 | /// 28 | /// The semi-unique identifier for this instance. This is 0 if the id has not yet been created. 29 | /// 30 | private int _id; 31 | 32 | [DebuggerNonUserCode] 33 | private bool GetStateForDebugger 34 | { 35 | get 36 | { 37 | return _tcs.Task.IsCompleted; 38 | } 39 | } 40 | 41 | /// 42 | /// Creates an async-compatible manual-reset event. 43 | /// 44 | /// Whether the manual-reset event is initially set or unset. 45 | public AsyncManualResetEvent(bool set) 46 | { 47 | _mutex = new object(); 48 | _tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 49 | if (set) 50 | _tcs.TrySetResult(null); 51 | } 52 | 53 | /// 54 | /// Creates an async-compatible manual-reset event that is initially unset. 55 | /// 56 | public AsyncManualResetEvent() 57 | : this(false) 58 | { 59 | } 60 | 61 | /// 62 | /// Gets a semi-unique identifier for this asynchronous manual-reset event. 63 | /// 64 | public int Id 65 | { 66 | get { return IdManager.GetId(ref _id); } 67 | } 68 | 69 | /// 70 | /// Whether this event is currently set. This member is seldom used; code using this member has a high possibility of race conditions. 71 | /// 72 | public bool IsSet 73 | { 74 | get { lock (_mutex) return _tcs.Task.IsCompleted; } 75 | } 76 | 77 | /// 78 | /// Asynchronously waits for this event to be set. 79 | /// 80 | public Task WaitAsync() 81 | { 82 | lock (_mutex) 83 | { 84 | return _tcs.Task; 85 | } 86 | } 87 | 88 | /// 89 | /// Asynchronously waits for this event to be set or for the wait to be canceled. 90 | /// 91 | /// The cancellation token used to cancel the wait. If this token is already canceled, this method will first check whether the event is set. 92 | public Task WaitAsync(CancellationToken cancellationToken) 93 | { 94 | var waitTask = WaitAsync(); 95 | if (waitTask.IsCompleted) 96 | return waitTask; 97 | return waitTask.WaitAsync(cancellationToken); 98 | } 99 | 100 | /// 101 | /// Synchronously waits for this event to be set. This method may block the calling thread. 102 | /// 103 | public void Wait() 104 | { 105 | WaitAsync().WaitAndUnwrapException(); 106 | } 107 | 108 | /// 109 | /// Synchronously waits for this event to be set. This method may block the calling thread. 110 | /// 111 | /// The cancellation token used to cancel the wait. If this token is already canceled, this method will first check whether the event is set. 112 | public void Wait(CancellationToken cancellationToken) 113 | { 114 | var ret = WaitAsync(CancellationToken.None); 115 | if (ret.IsCompleted) 116 | return; 117 | ret.WaitAndUnwrapException(cancellationToken); 118 | } 119 | 120 | #pragma warning disable CA1200 // Avoid using cref tags with a prefix 121 | /// 122 | /// Sets the event, atomically completing every task returned by . If the event is already set, this method does nothing. 123 | /// 124 | #pragma warning restore CA1200 // Avoid using cref tags with a prefix 125 | public void Set() 126 | { 127 | lock (_mutex) 128 | { 129 | _tcs.TrySetResult(null); 130 | } 131 | } 132 | 133 | /// 134 | /// Resets the event. If the event is already reset, this method does nothing. 135 | /// 136 | public void Reset() 137 | { 138 | lock (_mutex) 139 | { 140 | if (_tcs.Task.IsCompleted) 141 | _tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 142 | } 143 | } 144 | 145 | // ReSharper disable UnusedMember.Local 146 | [DebuggerNonUserCode] 147 | private sealed class DebugView 148 | { 149 | private readonly AsyncManualResetEvent _mre; 150 | 151 | public DebugView(AsyncManualResetEvent mre) 152 | { 153 | _mre = mre; 154 | } 155 | 156 | public int Id { get { return _mre.Id; } } 157 | 158 | public bool IsSet { get { return _mre.GetStateForDebugger; } } 159 | 160 | public Task CurrentTask { get { return _mre._tcs.Task; } } 161 | } 162 | // ReSharper restore UnusedMember.Local 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/AsyncMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Nito.AsyncEx 7 | { 8 | /// 9 | /// An async-compatible monitor. 10 | /// 11 | [DebuggerDisplay("Id = {Id}, ConditionVariableId = {_conditionVariable.Id}")] 12 | public sealed class AsyncMonitor 13 | { 14 | /// 15 | /// The lock. 16 | /// 17 | private readonly AsyncLock _asyncLock; 18 | 19 | /// 20 | /// The condition variable. 21 | /// 22 | private readonly AsyncConditionVariable _conditionVariable; 23 | 24 | /// 25 | /// Constructs a new monitor. 26 | /// 27 | /// The wait queue used to manage waiters for the lock. This may be null to use a default (FIFO) queue. 28 | /// The wait queue used to manage waiters for the signal. This may be null to use a default (FIFO) queue. 29 | internal AsyncMonitor(IAsyncWaitQueue? lockQueue, IAsyncWaitQueue? conditionVariableQueue) 30 | { 31 | _asyncLock = new AsyncLock(lockQueue); 32 | _conditionVariable = new AsyncConditionVariable(_asyncLock, conditionVariableQueue); 33 | } 34 | 35 | /// 36 | /// Constructs a new monitor. 37 | /// 38 | public AsyncMonitor() 39 | : this(null, null) 40 | { 41 | } 42 | 43 | /// 44 | /// Gets a semi-unique identifier for this monitor. 45 | /// 46 | public int Id 47 | { 48 | get { return _asyncLock.Id; } 49 | } 50 | 51 | /// 52 | /// Asynchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. 53 | /// 54 | /// The cancellation token used to cancel the enter. If this is already set, then this method will attempt to enter the monitor immediately (succeeding if the monitor is currently available). 55 | /// A disposable that leaves the monitor when disposed. 56 | public AwaitableDisposable EnterAsync(CancellationToken cancellationToken) 57 | { 58 | return _asyncLock.LockAsync(cancellationToken); 59 | } 60 | 61 | /// 62 | /// Asynchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. 63 | /// 64 | /// A disposable that leaves the monitor when disposed. 65 | public AwaitableDisposable EnterAsync() 66 | { 67 | return EnterAsync(CancellationToken.None); 68 | } 69 | 70 | /// 71 | /// Synchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. This method may block the calling thread. 72 | /// 73 | /// The cancellation token used to cancel the enter. If this is already set, then this method will attempt to enter the monitor immediately (succeeding if the monitor is currently available). 74 | public IDisposable Enter(CancellationToken cancellationToken) 75 | { 76 | return _asyncLock.Lock(cancellationToken); 77 | } 78 | 79 | /// 80 | /// Asynchronously enters the monitor. Returns a disposable that leaves the monitor when disposed. This method may block the calling thread. 81 | /// 82 | public IDisposable Enter() 83 | { 84 | return Enter(CancellationToken.None); 85 | } 86 | 87 | /// 88 | /// Asynchronously waits for a pulse signal on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns, even if the method is cancelled. This method internally will leave the monitor while waiting for a notification. 89 | /// 90 | /// The cancellation signal used to cancel this wait. 91 | public Task WaitAsync(CancellationToken cancellationToken) 92 | { 93 | return _conditionVariable.WaitAsync(cancellationToken); 94 | } 95 | 96 | /// 97 | /// Asynchronously waits for a pulse signal on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. This method internally will leave the monitor while waiting for a notification. 98 | /// 99 | public Task WaitAsync() 100 | { 101 | return WaitAsync(CancellationToken.None); 102 | } 103 | 104 | /// 105 | /// Asynchronously waits for a pulse signal on this monitor. This method may block the calling thread. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns, even if the method is cancelled. This method internally will leave the monitor while waiting for a notification. 106 | /// 107 | /// The cancellation signal used to cancel this wait. 108 | public void Wait(CancellationToken cancellationToken) 109 | { 110 | _conditionVariable.Wait(cancellationToken); 111 | } 112 | 113 | /// 114 | /// Asynchronously waits for a pulse signal on this monitor. This method may block the calling thread. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. This method internally will leave the monitor while waiting for a notification. 115 | /// 116 | public void Wait() 117 | { 118 | Wait(CancellationToken.None); 119 | } 120 | 121 | /// 122 | /// Sends a signal to a single task waiting on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. 123 | /// 124 | public void Pulse() 125 | { 126 | _conditionVariable.Notify(); 127 | } 128 | 129 | /// 130 | /// Sends a signal to all tasks waiting on this monitor. The monitor MUST already be entered when calling this method, and it will still be entered when this method returns. 131 | /// 132 | public void PulseAll() 133 | { 134 | _conditionVariable.NotifyAll(); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/IdManager.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace Nito.AsyncEx 4 | { 5 | /// 6 | /// Allocates Ids for instances on demand. 0 is an invalid/unassigned Id. Ids may be non-unique in very long-running systems. This is similar to the Id system used by and . 7 | /// 8 | /// The type for which ids are generated. 9 | // ReSharper disable UnusedTypeParameter 10 | internal static class IdManager 11 | // ReSharper restore UnusedTypeParameter 12 | { 13 | /// 14 | /// The last id generated for this type. This is 0 if no ids have been generated. 15 | /// 16 | // ReSharper disable StaticFieldInGenericType 17 | private static int _lastId; 18 | // ReSharper restore StaticFieldInGenericType 19 | 20 | /// 21 | /// Returns the id, allocating it if necessary. 22 | /// 23 | /// A reference to the field containing the id. 24 | public static int GetId(ref int id) 25 | { 26 | // If the Id has already been assigned, just use it. 27 | if (id != 0) 28 | return id; 29 | 30 | // Determine the new Id without modifying "id", since other threads may also be determining the new Id at the same time. 31 | int newId; 32 | 33 | // The Increment is in a while loop to ensure we get a non-zero Id: 34 | // If we are incrementing -1, then we want to skip over 0. 35 | // If there are tons of Id allocations going on, we want to skip over 0 no matter how many times we get it. 36 | do 37 | { 38 | newId = Interlocked.Increment(ref _lastId); 39 | } while (newId == 0); 40 | 41 | // Update the Id unless another thread already updated it. 42 | Interlocked.CompareExchange(ref id, newId, 0); 43 | 44 | // Return the current Id, regardless of whether it's our new Id or a new Id from another thread. 45 | return id; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/Nito.AsyncEx.Coordination.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Asynchronous coordination primitives. 5 | netstandard1.3;netstandard2.0;net461 6 | $(PackageTags);asynclock;asynclazy 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Coordination/PauseToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Nito.AsyncEx 6 | { 7 | /// 8 | /// The source (controller) of a "pause token", which can be used to cooperatively pause and unpause operations. 9 | /// 10 | public sealed class PauseTokenSource 11 | { 12 | /// 13 | /// The MRE that manages the "pause" logic. When the MRE is set, the token is not paused; when the MRE is not set, the token is paused. 14 | /// 15 | private readonly AsyncManualResetEvent _mre = new AsyncManualResetEvent(true); 16 | 17 | /// 18 | /// Whether or not this source (and its tokens) are in the paused state. This member is seldom used; code using this member has a high possibility of race conditions. 19 | /// 20 | public bool IsPaused 21 | { 22 | get { return !_mre.IsSet; } 23 | set 24 | { 25 | if (value) 26 | _mre.Reset(); 27 | else 28 | _mre.Set(); 29 | } 30 | } 31 | 32 | /// 33 | /// Gets a pause token controlled by this source. 34 | /// 35 | public PauseToken Token 36 | { 37 | get { return new PauseToken(_mre); } 38 | } 39 | } 40 | 41 | /// 42 | /// A type that allows an operation to be cooperatively paused. 43 | /// 44 | #pragma warning disable CA1815 // Override equals and operator equals on value types 45 | public struct PauseToken 46 | #pragma warning restore CA1815 // Override equals and operator equals on value types 47 | { 48 | /// 49 | /// The MRE that manages the "pause" logic, or null if this token can never be paused. When the MRE is set, the token is not paused; when the MRE is not set, the token is paused. 50 | /// 51 | private readonly AsyncManualResetEvent _mre; 52 | 53 | internal PauseToken(AsyncManualResetEvent mre) 54 | { 55 | _mre = mre; 56 | } 57 | 58 | /// 59 | /// Whether this token can ever possibly be paused. 60 | /// 61 | public bool CanBePaused 62 | { 63 | get { return _mre != null; } 64 | } 65 | 66 | /// 67 | /// Whether or not this token is in the paused state. 68 | /// 69 | public bool IsPaused 70 | { 71 | get { return _mre != null && !_mre.IsSet; } 72 | } 73 | 74 | /// 75 | /// Asynchronously waits until the pause token is not paused. 76 | /// 77 | public Task WaitWhilePausedAsync() 78 | { 79 | if (_mre == null) 80 | return TaskConstants.Completed; 81 | return _mre.WaitAsync(); 82 | } 83 | 84 | /// 85 | /// Asynchronously waits until the pause token is not paused, or until this wait is canceled by the cancellation token. 86 | /// 87 | /// The cancellation token to observe. If the token is already canceled, this method will first check if the pause token is unpaused, and will return without an exception in that case. 88 | public Task WaitWhilePausedAsync(CancellationToken token) 89 | { 90 | if (_mre == null) 91 | return TaskConstants.Completed; 92 | return _mre.WaitAsync(token); 93 | } 94 | 95 | /// 96 | /// Synchronously waits until the pause token is not paused. 97 | /// 98 | public void WaitWhilePaused() 99 | { 100 | if (_mre != null) 101 | _mre.Wait(); 102 | } 103 | 104 | /// 105 | /// Synchronously waits until the pause token is not paused, or until this wait is canceled by the cancellation token. 106 | /// 107 | /// The cancellation token to observe. If the token is already canceled, this method will first check if the pause token is unpaused, and will return without an exception in that case. 108 | public void WaitWhilePaused(CancellationToken token) 109 | { 110 | if (_mre != null) 111 | _mre.Wait(token); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Interop.WaitHandles/Interop/WaitHandleAsyncFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Nito.AsyncEx.Interop 6 | { 7 | /// 8 | /// Provides interop utilities for types. 9 | /// 10 | public static class WaitHandleAsyncFactory 11 | { 12 | /// 13 | /// Wraps a with a . When the is signalled, the returned is completed. If the handle is already signalled, this method acts synchronously. 14 | /// 15 | /// The to observe. 16 | public static Task FromWaitHandle(WaitHandle handle) 17 | { 18 | return FromWaitHandle(handle, Timeout.InfiniteTimeSpan, CancellationToken.None); 19 | } 20 | 21 | /// 22 | /// Wraps a with a . If the is signalled, the returned task is completed with a true result. If the observation times out, the returned task is completed with a false result. If the handle is already signalled or the timeout is zero, this method acts synchronously. 23 | /// 24 | /// The to observe. 25 | /// The timeout after which the is no longer observed. 26 | public static Task FromWaitHandle(WaitHandle handle, TimeSpan timeout) 27 | { 28 | return FromWaitHandle(handle, timeout, CancellationToken.None); 29 | } 30 | 31 | /// 32 | /// Wraps a with a . If the is signalled, the returned task is (successfully) completed. If the observation is cancelled, the returned task is cancelled. If the handle is already signalled or the cancellation token is already cancelled, this method acts synchronously. 33 | /// 34 | /// The to observe. 35 | /// The cancellation token that cancels observing the . 36 | public static Task FromWaitHandle(WaitHandle handle, CancellationToken token) 37 | { 38 | return FromWaitHandle(handle, Timeout.InfiniteTimeSpan, token); 39 | } 40 | 41 | /// 42 | /// Wraps a with a . If the is signalled, the returned task is completed with a true result. If the observation times out, the returned task is completed with a false result. If the observation is cancelled, the returned task is cancelled. If the handle is already signalled, the timeout is zero, or the cancellation token is already cancelled, then this method acts synchronously. 43 | /// 44 | /// The to observe. 45 | /// The timeout after which the is no longer observed. 46 | /// The cancellation token that cancels observing the . 47 | public static Task FromWaitHandle(WaitHandle handle, TimeSpan timeout, CancellationToken token) 48 | { 49 | _ = handle ?? throw new ArgumentNullException(nameof(handle)); 50 | 51 | // Handle synchronous cases. 52 | var alreadySignalled = handle.WaitOne(0); 53 | if (alreadySignalled) 54 | return TaskConstants.BooleanTrue; 55 | if (timeout == TimeSpan.Zero) 56 | return TaskConstants.BooleanFalse; 57 | if (token.IsCancellationRequested) 58 | return TaskConstants.Canceled; 59 | 60 | // Register all asynchronous cases. 61 | return DoFromWaitHandle(handle, timeout, token); 62 | } 63 | 64 | private static async Task DoFromWaitHandle(WaitHandle handle, TimeSpan timeout, CancellationToken token) 65 | { 66 | var tcs = new TaskCompletionSource(); 67 | using (new ThreadPoolRegistration(handle, timeout, tcs)) 68 | using (token.Register(state => ((TaskCompletionSource) state).TrySetCanceled(), tcs, useSynchronizationContext: false)) 69 | return await tcs.Task.ConfigureAwait(false); 70 | } 71 | 72 | private sealed class ThreadPoolRegistration : IDisposable 73 | { 74 | private readonly RegisteredWaitHandle _registeredWaitHandle; 75 | 76 | public ThreadPoolRegistration(WaitHandle handle, TimeSpan timeout, TaskCompletionSource tcs) 77 | { 78 | _registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(handle, 79 | (state, timedOut) => ((TaskCompletionSource)state).TrySetResult(!timedOut), tcs, 80 | timeout, executeOnlyOnce: true); 81 | } 82 | 83 | void IDisposable.Dispose() => _registeredWaitHandle.Unregister(null); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Interop.WaitHandles/Nito.AsyncEx.Interop.WaitHandles.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Task wrappers for WaitHandles. 5 | netstandard1.3;netstandard2.0;net461 6 | $(PackageTags);waithandle 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Oop/DeferralManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Nito.AsyncEx 5 | { 6 | /// 7 | /// A source for deferrals. Event argument types may implement this interface to indicate they understand async event handlers. 8 | /// 9 | public interface IDeferralSource 10 | { 11 | /// 12 | /// Requests a deferral. When the deferral is disposed, it is considered complete. 13 | /// 14 | IDisposable GetDeferral(); 15 | } 16 | 17 | /// 18 | /// Manages the deferrals for an event that may have asynchonous handlers and needs to know when they complete. Instances of this type may not be reused. 19 | /// 20 | public sealed class DeferralManager 21 | { 22 | /// 23 | /// The deferral source for deferrals managed by this manager. 24 | /// 25 | private readonly IDeferralSource _source; 26 | 27 | /// 28 | /// The lock protecting . 29 | /// 30 | private readonly object _mutex; 31 | 32 | /// 33 | /// The underlying countdown event. May be null if no deferrals were ever requested. 34 | /// 35 | private AsyncCountdownEvent? _ce; 36 | 37 | /// 38 | /// Creates a new deferral manager. 39 | /// 40 | public DeferralManager() 41 | { 42 | _source = new ManagedDeferralSource(this); 43 | _mutex = new object(); 44 | } 45 | 46 | /// 47 | /// Increments the count of active deferrals for this manager. 48 | /// 49 | internal void IncrementCount() 50 | { 51 | lock (_mutex) 52 | { 53 | if (_ce == null) 54 | _ce = new AsyncCountdownEvent(1); 55 | else 56 | _ce.AddCount(); 57 | } 58 | } 59 | 60 | /// 61 | /// Decrements the count of active deferrals for this manager. If the count reaches 0, then the manager notifies the code raising the event. 62 | /// 63 | internal void DecrementCount() 64 | { 65 | if (_ce == null) 66 | throw new InvalidOperationException("You must call IncrementCount before calling DecrementCount."); 67 | _ce.Signal(); 68 | } 69 | 70 | /// 71 | /// Gets a source for deferrals managed by this deferral manager. This is generally used to implement for event argument types. 72 | /// 73 | public IDeferralSource DeferralSource { get { return _source; } } 74 | 75 | /// 76 | /// Notifies the manager that all deferral requests have been made, and returns a task that is completed when all deferrals have completed. 77 | /// 78 | public Task WaitForDeferralsAsync() 79 | { 80 | lock (_mutex) 81 | { 82 | if (_ce == null) 83 | return TaskConstants.Completed; 84 | return _ce.WaitAsync(); 85 | } 86 | } 87 | 88 | /// 89 | /// A source for deferrals. 90 | /// 91 | private sealed class ManagedDeferralSource : IDeferralSource 92 | { 93 | /// 94 | /// The deferral manager in charge of this deferral source. 95 | /// 96 | private readonly DeferralManager _manager; 97 | 98 | public ManagedDeferralSource(DeferralManager manager) 99 | { 100 | _manager = manager; 101 | } 102 | 103 | IDisposable IDeferralSource.GetDeferral() 104 | { 105 | _manager.IncrementCount(); 106 | return new Deferral(_manager); 107 | } 108 | 109 | /// 110 | /// A deferral. 111 | /// 112 | private sealed class Deferral : Disposables.SingleDisposable 113 | { 114 | public Deferral(DeferralManager manager) 115 | : base(manager) 116 | { 117 | } 118 | 119 | protected override void Dispose(DeferralManager context) 120 | { 121 | context.DecrementCount(); 122 | } 123 | } 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Oop/Nito.AsyncEx.Oop.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Interfaces and utility methods for combining async with OOP. 5 | netstandard1.3;netstandard2.0;net461 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/AwaitableDisposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Nito.AsyncEx 6 | { 7 | /// 8 | /// An awaitable wrapper around a task whose result is disposable. The wrapper is not disposable, so this prevents usage errors like "using (MyAsync())" when the appropriate usage should be "using (await MyAsync())". 9 | /// 10 | /// The type of the result of the underlying task. 11 | #pragma warning disable CA1815 // Override equals and operator equals on value types 12 | public readonly struct AwaitableDisposable where T : IDisposable 13 | #pragma warning restore CA1815 // Override equals and operator equals on value types 14 | { 15 | /// 16 | /// The underlying task. 17 | /// 18 | private readonly Task _task; 19 | 20 | /// 21 | /// Initializes a new awaitable wrapper around the specified task. 22 | /// 23 | /// The underlying task to wrap. This may not be null. 24 | public AwaitableDisposable(Task task) 25 | { 26 | if (task == null) 27 | throw new ArgumentNullException(nameof(task)); 28 | _task = task; 29 | } 30 | 31 | /// 32 | /// Returns the underlying task. 33 | /// 34 | public Task AsTask() 35 | { 36 | return _task; 37 | } 38 | 39 | /// 40 | /// Implicit conversion to the underlying task. 41 | /// 42 | /// The awaitable wrapper. 43 | #pragma warning disable CA2225 // Operator overloads have named alternates 44 | public static implicit operator Task(AwaitableDisposable source) 45 | #pragma warning restore CA2225 // Operator overloads have named alternates 46 | { 47 | return source.AsTask(); 48 | } 49 | 50 | /// 51 | /// Infrastructure. Returns the task awaiter for the underlying task. 52 | /// 53 | public TaskAwaiter GetAwaiter() 54 | { 55 | return _task.GetAwaiter(); 56 | } 57 | 58 | /// 59 | /// Infrastructure. Returns a configured task awaiter for the underlying task. 60 | /// 61 | /// Whether to attempt to marshal the continuation back to the captured context. 62 | public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) 63 | { 64 | return _task.ConfigureAwait(continueOnCapturedContext); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/CancellationTokenTaskSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Nito.AsyncEx 6 | { 7 | /// 8 | /// Holds the task for a cancellation token, as well as the token registration. The registration is disposed when this instance is disposed. 9 | /// 10 | public sealed class CancellationTokenTaskSource : IDisposable 11 | { 12 | /// 13 | /// The cancellation token registration, if any. This is null if the registration was not necessary. 14 | /// 15 | private readonly IDisposable? _registration; 16 | 17 | /// 18 | /// Creates a task for the specified cancellation token, registering with the token if necessary. 19 | /// 20 | /// The cancellation token to observe. 21 | public CancellationTokenTaskSource(CancellationToken cancellationToken) 22 | { 23 | if (cancellationToken.IsCancellationRequested) 24 | { 25 | Task = System.Threading.Tasks.Task.FromCanceled(cancellationToken); 26 | return; 27 | } 28 | var tcs = new TaskCompletionSource(); 29 | _registration = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken), useSynchronizationContext: false); 30 | Task = tcs.Task; 31 | } 32 | 33 | /// 34 | /// Gets the task for the source cancellation token. 35 | /// 36 | public Task Task { get; private set; } 37 | 38 | /// 39 | /// Disposes the cancellation token registration, if any. Note that this may cause to never complete. 40 | /// 41 | public void Dispose() 42 | { 43 | _registration?.Dispose(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/ExceptionHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ExceptionServices; 3 | 4 | internal static class ExceptionHelpers 5 | { 6 | /// 7 | /// Attempts to prepare the exception for re-throwing by preserving the stack trace. The returned exception should be immediately thrown. 8 | /// 9 | /// The exception. May not be null. 10 | /// The that was passed into this method. 11 | public static Exception PrepareForRethrow(Exception exception) 12 | { 13 | ExceptionDispatchInfo.Capture(exception).Throw(); 14 | 15 | // The code cannot ever get here. We just return a value to work around a badly-designed API (ExceptionDispatchInfo.Throw): 16 | // https://connect.microsoft.com/VisualStudio/feedback/details/689516/exceptiondispatchinfo-api-modifications (http://www.webcitation.org/6XQ7RoJmO) 17 | return exception; 18 | } 19 | } -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/Interop/ApmAsyncFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx.Synchronous; 4 | 5 | namespace Nito.AsyncEx.Interop 6 | { 7 | /// 8 | /// Creation methods for tasks wrapping the Asynchronous Programming Model (APM), and APM wrapper methods around tasks. 9 | /// 10 | public static class ApmAsyncFactory 11 | { 12 | /// 13 | /// Wraps a into the Begin method of an APM pattern. 14 | /// 15 | /// The task to wrap. 16 | /// The callback method passed into the Begin method of the APM pattern. 17 | /// The state passed into the Begin method of the APM pattern. 18 | /// The asynchronous operation, to be returned by the Begin method of the APM pattern. 19 | public static IAsyncResult ToBegin(Task task, AsyncCallback callback, object state) 20 | { 21 | var tcs = new TaskCompletionSource(state, TaskCreationOptions.RunContinuationsAsynchronously); 22 | SynchronizationContextSwitcher.NoContext(() => CompleteAsync(task, callback, tcs)); 23 | return tcs.Task; 24 | } 25 | 26 | // `async void` is on purpose, to raise `callback` exceptions directly on the thread pool. 27 | private static async void CompleteAsync(Task task, AsyncCallback callback, TaskCompletionSource tcs) 28 | { 29 | try 30 | { 31 | await task.ConfigureAwait(false); 32 | tcs.TrySetResult(null); 33 | } 34 | catch (OperationCanceledException ex) 35 | { 36 | tcs.TrySetCanceled(ex.CancellationToken); 37 | } 38 | #pragma warning disable CA1031 // Do not catch general exception types 39 | catch (Exception ex) 40 | #pragma warning restore CA1031 // Do not catch general exception types 41 | { 42 | tcs.TrySetException(ex); 43 | } 44 | finally 45 | { 46 | callback?.Invoke(tcs.Task); 47 | } 48 | } 49 | 50 | /// 51 | /// Wraps a into the End method of an APM pattern. 52 | /// 53 | /// The asynchronous operation returned by the matching Begin method of this APM pattern. 54 | /// The result of the asynchronous operation, to be returned by the End method of the APM pattern. 55 | public static void ToEnd(IAsyncResult asyncResult) 56 | { 57 | ((Task)asyncResult).WaitAndUnwrapException(); 58 | } 59 | 60 | /// 61 | /// Wraps a into the Begin method of an APM pattern. 62 | /// 63 | /// The task to wrap. May not be null. 64 | /// The callback method passed into the Begin method of the APM pattern. 65 | /// The state passed into the Begin method of the APM pattern. 66 | /// The asynchronous operation, to be returned by the Begin method of the APM pattern. 67 | public static IAsyncResult ToBegin(Task task, AsyncCallback callback, object state) 68 | { 69 | var tcs = new TaskCompletionSource(state, TaskCreationOptions.RunContinuationsAsynchronously); 70 | SynchronizationContextSwitcher.NoContext(() => CompleteAsync(task, callback, tcs)); 71 | return tcs.Task; 72 | } 73 | 74 | // `async void` is on purpose, to raise `callback` exceptions directly on the thread pool. 75 | private static async void CompleteAsync(Task task, AsyncCallback callback, TaskCompletionSource tcs) 76 | { 77 | try 78 | { 79 | tcs.TrySetResult(await task.ConfigureAwait(false)); 80 | } 81 | catch (OperationCanceledException ex) 82 | { 83 | tcs.TrySetCanceled(ex.CancellationToken); 84 | } 85 | #pragma warning disable CA1031 // Do not catch general exception types 86 | catch (Exception ex) 87 | #pragma warning restore CA1031 // Do not catch general exception types 88 | { 89 | tcs.TrySetException(ex); 90 | } 91 | finally 92 | { 93 | callback?.Invoke(tcs.Task); 94 | } 95 | } 96 | 97 | /// 98 | /// Wraps a into the End method of an APM pattern. 99 | /// 100 | /// The asynchronous operation returned by the matching Begin method of this APM pattern. 101 | /// The result of the asynchronous operation, to be returned by the End method of the APM pattern. 102 | public static TResult ToEnd(IAsyncResult asyncResult) 103 | { 104 | return ((Task)asyncResult).WaitAndUnwrapException(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/Interop/EventArguments.cs: -------------------------------------------------------------------------------- 1 | namespace Nito.AsyncEx.Interop 2 | { 3 | /// 4 | /// Arguments passed to a .NET event that follows the standard sender, arguments event pattern. 5 | /// 6 | /// The type of the sender of the event. This is commonly . 7 | /// The type of the event arguments. This is commonly or a derived type. 8 | #pragma warning disable CA1815 // Override equals and operator equals on value types 9 | public struct EventArguments 10 | #pragma warning restore CA1815 // Override equals and operator equals on value types 11 | { 12 | /// 13 | /// The sender of the event. 14 | /// 15 | public TSender Sender { get; set; } 16 | 17 | /// 18 | /// The event arguments. 19 | /// 20 | public TEventArgs EventArgs { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/Nito.AsyncEx.Tasks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Common helper methods for tasks as used in asynchronous programming. 5 | netstandard1.3;netstandard2.0;net461 6 | $(PackageTags);taskfactory;cancellationtoken;taskcompletionsource 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/SemaphoreSlimExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Nito.Disposables; 7 | 8 | namespace Nito.AsyncEx 9 | { 10 | /// 11 | /// Provides extension methods for . 12 | /// 13 | public static class SemaphoreSlimExtensions 14 | { 15 | private static async Task DoLockAsync(SemaphoreSlim @this, CancellationToken cancellationToken) 16 | { 17 | await @this.WaitAsync(cancellationToken).ConfigureAwait(false); 18 | return Disposable.Create(() => @this.Release()); 19 | } 20 | 21 | /// 22 | /// Asynchronously waits on the semaphore, and returns a disposable that releases the semaphore when disposed, thus treating this semaphore as a "multi-lock". 23 | /// 24 | /// The semaphore to lock. 25 | /// The cancellation token used to cancel the wait. 26 | public static AwaitableDisposable LockAsync(this SemaphoreSlim @this, CancellationToken cancellationToken) 27 | { 28 | _ = @this ?? throw new ArgumentNullException(nameof(@this)); 29 | #pragma warning disable CA2000 // Dispose objects before losing scope 30 | return new AwaitableDisposable(DoLockAsync(@this, cancellationToken)); 31 | #pragma warning restore CA2000 // Dispose objects before losing scope 32 | } 33 | 34 | /// 35 | /// Asynchronously waits on the semaphore, and returns a disposable that releases the semaphore when disposed, thus treating this semaphore as a "multi-lock". 36 | /// 37 | public static AwaitableDisposable LockAsync(this SemaphoreSlim @this) => @this.LockAsync(CancellationToken.None); 38 | 39 | /// 40 | /// Synchronously waits on the semaphore, and returns a disposable that releases the semaphore when disposed, thus treating this semaphore as a "multi-lock". 41 | /// 42 | /// The semaphore to lock. 43 | /// The cancellation token used to cancel the wait. 44 | public static IDisposable Lock(this SemaphoreSlim @this, CancellationToken cancellationToken) 45 | { 46 | _ = @this ?? throw new ArgumentNullException(nameof(@this)); 47 | @this.Wait(cancellationToken); 48 | return Disposable.Create(() => @this.Release()); 49 | } 50 | 51 | /// 52 | /// Synchronously waits on the semaphore, and returns a disposable that releases the semaphore when disposed, thus treating this semaphore as a "multi-lock". 53 | /// 54 | /// The semaphore to lock. 55 | public static IDisposable Lock(this SemaphoreSlim @this) => @this.Lock(CancellationToken.None); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/SynchronizationContextSwitcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Nito.AsyncEx 6 | { 7 | /// 8 | /// Utility class for temporarily switching implementations. 9 | /// 10 | public sealed class SynchronizationContextSwitcher : Disposables.SingleDisposable 11 | { 12 | /// 13 | /// The previous . 14 | /// 15 | private readonly SynchronizationContext? _oldContext; 16 | 17 | /// 18 | /// Initializes a new instance of the class, installing the new . 19 | /// 20 | /// The new . This can be null to remove an existing . 21 | private SynchronizationContextSwitcher(SynchronizationContext? newContext) 22 | : base(new object()) 23 | { 24 | _oldContext = SynchronizationContext.Current; 25 | SynchronizationContext.SetSynchronizationContext(newContext); 26 | } 27 | 28 | /// 29 | /// Restores the old . 30 | /// 31 | protected override void Dispose(object context) 32 | { 33 | SynchronizationContext.SetSynchronizationContext(_oldContext); 34 | } 35 | 36 | /// 37 | /// Executes a synchronous delegate without the current . The current context is restored when this function returns. 38 | /// 39 | /// The delegate to execute. 40 | public static void NoContext(Action action) 41 | { 42 | _ = action ?? throw new ArgumentNullException(nameof(action)); 43 | using (new SynchronizationContextSwitcher(null)) 44 | action(); 45 | } 46 | 47 | /// 48 | /// Executes a synchronous or asynchronous delegate without the current . The current context is restored when this function synchronously returns. 49 | /// 50 | /// The delegate to execute. 51 | public static T NoContext(Func action) 52 | { 53 | _ = action ?? throw new ArgumentNullException(nameof(action)); 54 | using (new SynchronizationContextSwitcher(null)) 55 | return action(); 56 | } 57 | 58 | /// 59 | /// Executes a synchronous delegate with the specified as "current". The previous current context is restored when this function returns. 60 | /// 61 | /// The context to treat as "current". May be null to indicate the thread pool context. 62 | /// The delegate to execute. 63 | public static void ApplyContext(SynchronizationContext context, Action action) 64 | { 65 | _ = action ?? throw new ArgumentNullException(nameof(action)); 66 | using (new SynchronizationContextSwitcher(context)) 67 | action(); 68 | } 69 | 70 | /// 71 | /// Executes a synchronous or asynchronous delegate without the specified as "current". The previous current context is restored when this function synchronously returns. 72 | /// 73 | /// The context to treat as "current". May be null to indicate the thread pool context. 74 | /// The delegate to execute. 75 | public static T ApplyContext(SynchronizationContext context, Func action) 76 | { 77 | _ = action ?? throw new ArgumentNullException(nameof(action)); 78 | using (new SynchronizationContextSwitcher(context)) 79 | return action(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/Synchronous/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.ExceptionServices; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Nito.AsyncEx.Synchronous 7 | { 8 | /// 9 | /// Provides synchronous extension methods for tasks. 10 | /// 11 | public static class TaskExtensions 12 | { 13 | /// 14 | /// Waits for the task to complete, unwrapping any exceptions. 15 | /// 16 | /// The task. May not be null. 17 | public static void WaitAndUnwrapException(this Task task) 18 | { 19 | if (task == null) 20 | throw new ArgumentNullException(nameof(task)); 21 | task.GetAwaiter().GetResult(); 22 | } 23 | 24 | /// 25 | /// Waits for the task to complete, unwrapping any exceptions. 26 | /// 27 | /// The task. May not be null. 28 | /// A cancellation token to observe while waiting for the task to complete. 29 | /// The was cancelled before the completed, or the raised an . 30 | public static void WaitAndUnwrapException(this Task task, CancellationToken cancellationToken) 31 | { 32 | if (task == null) 33 | throw new ArgumentNullException(nameof(task)); 34 | try 35 | { 36 | task.Wait(cancellationToken); 37 | } 38 | catch (AggregateException ex) 39 | { 40 | throw ExceptionHelpers.PrepareForRethrow(ex.InnerException); 41 | } 42 | } 43 | 44 | /// 45 | /// Waits for the task to complete, unwrapping any exceptions. 46 | /// 47 | /// The type of the result of the task. 48 | /// The task. May not be null. 49 | /// The result of the task. 50 | public static TResult WaitAndUnwrapException(this Task task) 51 | { 52 | if (task == null) 53 | throw new ArgumentNullException(nameof(task)); 54 | return task.GetAwaiter().GetResult(); 55 | } 56 | 57 | /// 58 | /// Waits for the task to complete, unwrapping any exceptions. 59 | /// 60 | /// The type of the result of the task. 61 | /// The task. May not be null. 62 | /// A cancellation token to observe while waiting for the task to complete. 63 | /// The result of the task. 64 | /// The was cancelled before the completed, or the raised an . 65 | public static TResult WaitAndUnwrapException(this Task task, CancellationToken cancellationToken) 66 | { 67 | if (task == null) 68 | throw new ArgumentNullException(nameof(task)); 69 | try 70 | { 71 | task.Wait(cancellationToken); 72 | return task.Result; 73 | } 74 | catch (AggregateException ex) 75 | { 76 | throw ExceptionHelpers.PrepareForRethrow(ex.InnerException); 77 | } 78 | } 79 | 80 | /// 81 | /// Waits for the task to complete, but does not raise task exceptions. The task exception (if any) is unobserved. 82 | /// 83 | /// The task. May not be null. 84 | public static void WaitWithoutException(this Task task) 85 | { 86 | if (task == null) 87 | throw new ArgumentNullException(nameof(task)); 88 | try 89 | { 90 | task.Wait(); 91 | } 92 | catch (AggregateException) 93 | { 94 | } 95 | } 96 | 97 | /// 98 | /// Waits for the task to complete, but does not raise task exceptions. The task exception (if any) is unobserved. 99 | /// 100 | /// The task. May not be null. 101 | /// A cancellation token to observe while waiting for the task to complete. 102 | /// The was cancelled before the completed. 103 | public static void WaitWithoutException(this Task task, CancellationToken cancellationToken) 104 | { 105 | if (task == null) 106 | throw new ArgumentNullException(nameof(task)); 107 | try 108 | { 109 | task.Wait(cancellationToken); 110 | } 111 | catch (AggregateException) 112 | { 113 | cancellationToken.ThrowIfCancellationRequested(); 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/TaskCompletionSourceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx.Synchronous; 4 | 5 | namespace Nito.AsyncEx 6 | { 7 | /// 8 | /// Provides extension methods for . 9 | /// 10 | public static class TaskCompletionSourceExtensions 11 | { 12 | /// 13 | /// Attempts to complete a , propagating the completion of . 14 | /// 15 | /// The type of the result of the target asynchronous operation. 16 | /// The type of the result of the source asynchronous operation. 17 | /// The task completion source. May not be null. 18 | /// The task. May not be null. 19 | /// true if this method completed the task completion source; false if it was already completed. 20 | public static bool TryCompleteFromCompletedTask(this TaskCompletionSource @this, Task task) where TSourceResult : TResult 21 | { 22 | if (@this == null) 23 | throw new ArgumentNullException(nameof(@this)); 24 | if (task == null) 25 | throw new ArgumentNullException(nameof(task)); 26 | 27 | if (task.IsFaulted) 28 | return @this.TrySetException(task.Exception.InnerExceptions); 29 | if (task.IsCanceled) 30 | { 31 | try 32 | { 33 | task.WaitAndUnwrapException(); 34 | } 35 | catch (OperationCanceledException exception) 36 | { 37 | var token = exception.CancellationToken; 38 | return token.IsCancellationRequested ? @this.TrySetCanceled(token) : @this.TrySetCanceled(); 39 | } 40 | } 41 | return @this.TrySetResult(task.Result); 42 | } 43 | 44 | /// 45 | /// Attempts to complete a , propagating the completion of but using the result value from if the task completed successfully. 46 | /// 47 | /// The type of the result of the target asynchronous operation. 48 | /// The task completion source. May not be null. 49 | /// The task. May not be null. 50 | /// A delegate that returns the result with which to complete the task completion source, if the task completed successfully. May not be null. 51 | /// true if this method completed the task completion source; false if it was already completed. 52 | public static bool TryCompleteFromCompletedTask(this TaskCompletionSource @this, Task task, Func resultFunc) 53 | { 54 | if (@this == null) 55 | throw new ArgumentNullException(nameof(@this)); 56 | if (task == null) 57 | throw new ArgumentNullException(nameof(task)); 58 | if (resultFunc == null) 59 | throw new ArgumentNullException(nameof(resultFunc)); 60 | 61 | if (task.IsFaulted) 62 | return @this.TrySetException(task.Exception.InnerExceptions); 63 | if (task.IsCanceled) 64 | { 65 | try 66 | { 67 | task.WaitAndUnwrapException(); 68 | } 69 | catch (OperationCanceledException exception) 70 | { 71 | var token = exception.CancellationToken; 72 | return token.IsCancellationRequested ? @this.TrySetCanceled(token) : @this.TrySetCanceled(); 73 | } 74 | } 75 | return @this.TrySetResult(resultFunc()); 76 | } 77 | 78 | /// 79 | /// Creates a new TCS for use with async code, and which forces its continuations to execute asynchronously. 80 | /// 81 | /// The type of the result of the TCS. 82 | public static TaskCompletionSource CreateAsyncTaskSource() 83 | { 84 | return new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/TaskConstants.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Nito.AsyncEx 5 | { 6 | /// 7 | /// Provides completed task constants. 8 | /// 9 | public static class TaskConstants 10 | { 11 | private static readonly Task booleanTrue = Task.FromResult(true); 12 | private static readonly Task intNegativeOne = Task.FromResult(-1); 13 | 14 | /// 15 | /// A task that has been completed with the value true. 16 | /// 17 | public static Task BooleanTrue 18 | { 19 | get 20 | { 21 | return booleanTrue; 22 | } 23 | } 24 | 25 | /// 26 | /// A task that has been completed with the value false. 27 | /// 28 | public static Task BooleanFalse 29 | { 30 | get 31 | { 32 | return TaskConstants.Default; 33 | } 34 | } 35 | 36 | /// 37 | /// A task that has been completed with the value 0. 38 | /// 39 | public static Task Int32Zero 40 | { 41 | get 42 | { 43 | return TaskConstants.Default; 44 | } 45 | } 46 | 47 | /// 48 | /// A task that has been completed with the value -1. 49 | /// 50 | public static Task Int32NegativeOne 51 | { 52 | get 53 | { 54 | return intNegativeOne; 55 | } 56 | } 57 | 58 | /// 59 | /// A that has been completed. 60 | /// 61 | public static Task Completed 62 | { 63 | get 64 | { 65 | return Task.CompletedTask; 66 | } 67 | } 68 | 69 | /// 70 | /// A task that has been canceled. 71 | /// 72 | public static Task Canceled 73 | { 74 | get 75 | { 76 | return TaskConstants.Canceled; 77 | } 78 | } 79 | } 80 | 81 | /// 82 | /// Provides completed task constants. 83 | /// 84 | /// The type of the task result. 85 | public static class TaskConstants 86 | { 87 | private static readonly Task defaultValue = Task.FromResult(default(T)!); 88 | private static readonly Task canceled = Task.FromCanceled(new CancellationToken(true)); 89 | 90 | /// 91 | /// A task that has been completed with the default value of . 92 | /// 93 | #pragma warning disable CA1000 // Do not declare static members on generic types 94 | public static Task Default 95 | #pragma warning restore CA1000 // Do not declare static members on generic types 96 | { 97 | get 98 | { 99 | return defaultValue; 100 | } 101 | } 102 | 103 | /// 104 | /// A task that has been canceled. 105 | /// 106 | #pragma warning disable CA1000 // Do not declare static members on generic types 107 | public static Task Canceled 108 | #pragma warning restore CA1000 // Do not declare static members on generic types 109 | { 110 | get 111 | { 112 | return canceled; 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/TaskFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Nito.AsyncEx 5 | { 6 | /// 7 | /// Provides extension methods for task factories. 8 | /// 9 | public static class TaskFactoryExtensions 10 | { 11 | /// 12 | /// Queues work to the task factory and returns a representing that work. If the task factory does not specify a task scheduler, the thread pool task scheduler is used. 13 | /// 14 | /// The . May not be null. 15 | /// The action delegate to execute. May not be null. 16 | /// The started task. 17 | public static Task Run(this TaskFactory @this, Action action) 18 | { 19 | if (@this == null) 20 | throw new ArgumentNullException(nameof(@this)); 21 | if (action == null) 22 | throw new ArgumentNullException(nameof(action)); 23 | 24 | return @this.StartNew(action, @this.CancellationToken, @this.CreationOptions | TaskCreationOptions.DenyChildAttach, @this.Scheduler ?? TaskScheduler.Default); 25 | } 26 | 27 | /// 28 | /// Queues work to the task factory and returns a representing that work. If the task factory does not specify a task scheduler, the thread pool task scheduler is used. 29 | /// 30 | /// The . May not be null. 31 | /// The action delegate to execute. May not be null. 32 | /// The started task. 33 | public static Task Run(this TaskFactory @this, Func action) 34 | { 35 | if (@this == null) 36 | throw new ArgumentNullException(nameof(@this)); 37 | if (action == null) 38 | throw new ArgumentNullException(nameof(action)); 39 | 40 | return @this.StartNew(action, @this.CancellationToken, @this.CreationOptions | TaskCreationOptions.DenyChildAttach, @this.Scheduler ?? TaskScheduler.Default); 41 | } 42 | 43 | /// 44 | /// Queues work to the task factory and returns a proxy representing that work. If the task factory does not specify a task scheduler, the thread pool task scheduler is used. 45 | /// 46 | /// The . May not be null. 47 | /// The action delegate to execute. May not be null. 48 | /// The started task. 49 | public static Task Run(this TaskFactory @this, Func action) 50 | { 51 | if (@this == null) 52 | throw new ArgumentNullException(nameof(@this)); 53 | if (action == null) 54 | throw new ArgumentNullException(nameof(action)); 55 | 56 | return @this.StartNew(action, @this.CancellationToken, @this.CreationOptions | TaskCreationOptions.DenyChildAttach, @this.Scheduler ?? TaskScheduler.Default).Unwrap(); 57 | } 58 | 59 | /// 60 | /// Queues work to the task factory and returns a proxy representing that work. If the task factory does not specify a task scheduler, the thread pool task scheduler is used. 61 | /// 62 | /// The . May not be null. 63 | /// The action delegate to execute. May not be null. 64 | /// The started task. 65 | public static Task Run(this TaskFactory @this, Func> action) 66 | { 67 | if (@this == null) 68 | throw new ArgumentNullException(nameof(@this)); 69 | if (action == null) 70 | throw new ArgumentNullException(nameof(action)); 71 | 72 | return @this.StartNew(action, @this.CancellationToken, @this.CreationOptions | TaskCreationOptions.DenyChildAttach, @this.Scheduler ?? TaskScheduler.Default).Unwrap(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx.Tasks/TaskHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Nito.AsyncEx 5 | { 6 | /// 7 | /// Helper methods for working with tasks. 8 | /// 9 | public static class TaskHelper 10 | { 11 | /// 12 | /// Executes a delegate synchronously, and captures its result in a task. The returned task is already completed. 13 | /// 14 | /// The delegate to execute synchronously. 15 | #pragma warning disable 1998 16 | public static async Task ExecuteAsTask(Action func) 17 | #pragma warning restore 1998 18 | { 19 | _ = func ?? throw new ArgumentNullException(nameof(func)); 20 | func(); 21 | } 22 | 23 | /// 24 | /// Executes a delegate synchronously, and captures its result in a task. The returned task is already completed. 25 | /// 26 | /// The delegate to execute synchronously. 27 | #pragma warning disable 1998 28 | public static async Task ExecuteAsTask(Func func) 29 | #pragma warning restore 1998 30 | { 31 | _ = func ?? throw new ArgumentNullException(nameof(func)); 32 | return func(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Nito.AsyncEx/Nito.AsyncEx.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Async and Task Helpers 5 | A helper library for the Task-Based Asynchronous Pattern (TAP). 6 | netstandard1.3;netstandard2.0;net461 7 | async;await;async-await;task;asynchronous;nito;nitro 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StephenCleary/AsyncEx/0361015459938f2eb8f3c1ad1021d19ee01c93a4/src/icon.png -------------------------------------------------------------------------------- /src/project.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5.1.2 4 | Stephen Cleary 5 | task;async 6 | Nito.AsyncEx 7 | 8 | -------------------------------------------------------------------------------- /test/AsyncEx.Context.UnitTests/AsyncContextThreadUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | 9 | namespace UnitTests 10 | { 11 | public class AsyncContextThreadUnitTests 12 | { 13 | [Fact] 14 | public async Task AsyncContextThread_IsAnIndependentThread() 15 | { 16 | var testThread = Thread.CurrentThread.ManagedThreadId; 17 | var thread = new AsyncContextThread(); 18 | var contextThread = await thread.Factory.Run(() => Thread.CurrentThread.ManagedThreadId); 19 | Assert.NotEqual(testThread, contextThread); 20 | await thread.JoinAsync(); 21 | } 22 | 23 | [Fact] 24 | public async Task AsyncDelegate_ResumesOnSameThread() 25 | { 26 | var thread = new AsyncContextThread(); 27 | int contextThread = -1, resumeThread = -1; 28 | await thread.Factory.Run(async () => 29 | { 30 | contextThread = Thread.CurrentThread.ManagedThreadId; 31 | await Task.Yield(); 32 | resumeThread = Thread.CurrentThread.ManagedThreadId; 33 | }); 34 | Assert.Equal(contextThread, resumeThread); 35 | await thread.JoinAsync(); 36 | } 37 | 38 | [Fact] 39 | public async Task Join_StopsTask() 40 | { 41 | var context = new AsyncContextThread(); 42 | var thread = await context.Factory.Run(() => Thread.CurrentThread); 43 | await context.JoinAsync(); 44 | } 45 | 46 | [Fact] 47 | public async Task Context_IsCorrectAsyncContext() 48 | { 49 | using (var thread = new AsyncContextThread()) 50 | { 51 | var observedContext = await thread.Factory.Run(() => AsyncContext.Current); 52 | Assert.Same(observedContext, thread.Context); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/AsyncEx.Context.UnitTests/AsyncEx.Context.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net461 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncAutoResetEventUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using Nito.AsyncEx.Synchronous; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Diagnostics.CodeAnalysis; 8 | using Xunit; 9 | using Nito.AsyncEx.Testing; 10 | 11 | namespace UnitTests 12 | { 13 | public class AsyncAutoResetEventUnitTests 14 | { 15 | [Fact] 16 | public async Task WaitAsync_Unset_IsNotCompleted() 17 | { 18 | var are = new AsyncAutoResetEvent(); 19 | 20 | var task = are.WaitAsync(); 21 | 22 | await AsyncAssert.NeverCompletesAsync(task); 23 | } 24 | 25 | [Fact] 26 | public void WaitAsync_AfterSet_CompletesSynchronously() 27 | { 28 | var are = new AsyncAutoResetEvent(); 29 | 30 | are.Set(); 31 | var task = are.WaitAsync(); 32 | 33 | Assert.True(task.IsCompleted); 34 | } 35 | 36 | [Fact] 37 | public void WaitAsync_Set_CompletesSynchronously() 38 | { 39 | var are = new AsyncAutoResetEvent(true); 40 | 41 | var task = are.WaitAsync(); 42 | 43 | Assert.True(task.IsCompleted); 44 | } 45 | 46 | [Fact] 47 | public async Task MultipleWaitAsync_AfterSet_OnlyOneIsCompleted() 48 | { 49 | var are = new AsyncAutoResetEvent(); 50 | 51 | are.Set(); 52 | var task1 = are.WaitAsync(); 53 | var task2 = are.WaitAsync(); 54 | 55 | Assert.True(task1.IsCompleted); 56 | await AsyncAssert.NeverCompletesAsync(task2); 57 | } 58 | 59 | [Fact] 60 | public async Task MultipleWaitAsync_Set_OnlyOneIsCompleted() 61 | { 62 | var are = new AsyncAutoResetEvent(true); 63 | 64 | var task1 = are.WaitAsync(); 65 | var task2 = are.WaitAsync(); 66 | 67 | Assert.True(task1.IsCompleted); 68 | await AsyncAssert.NeverCompletesAsync(task2); 69 | } 70 | 71 | [Fact] 72 | public async Task MultipleWaitAsync_AfterMultipleSet_OnlyOneIsCompleted() 73 | { 74 | var are = new AsyncAutoResetEvent(); 75 | 76 | are.Set(); 77 | are.Set(); 78 | var task1 = are.WaitAsync(); 79 | var task2 = are.WaitAsync(); 80 | 81 | Assert.True(task1.IsCompleted); 82 | await AsyncAssert.NeverCompletesAsync(task2); 83 | } 84 | 85 | [Fact] 86 | public void WaitAsync_PreCancelled_Set_SynchronouslyCompletesWait() 87 | { 88 | var are = new AsyncAutoResetEvent(true); 89 | var token = new CancellationToken(true); 90 | 91 | var task = are.WaitAsync(token); 92 | 93 | Assert.True(task.IsCompleted); 94 | Assert.False(task.IsCanceled); 95 | Assert.False(task.IsFaulted); 96 | } 97 | 98 | [Fact] 99 | public async Task WaitAsync_Cancelled_DoesNotAutoReset() 100 | { 101 | var are = new AsyncAutoResetEvent(); 102 | var cts = new CancellationTokenSource(); 103 | 104 | cts.Cancel(); 105 | var task1 = are.WaitAsync(cts.Token); 106 | task1.WaitWithoutException(); 107 | are.Set(); 108 | var task2 = are.WaitAsync(); 109 | 110 | await task2; 111 | } 112 | 113 | [Fact] 114 | public void WaitAsync_PreCancelled_Unset_SynchronouslyCancels() 115 | { 116 | var are = new AsyncAutoResetEvent(false); 117 | var token = new CancellationToken(true); 118 | 119 | var task = are.WaitAsync(token); 120 | 121 | Assert.True(task.IsCompleted); 122 | Assert.True(task.IsCanceled); 123 | Assert.False(task.IsFaulted); 124 | } 125 | 126 | #if TODO 127 | [Fact] 128 | public void WaitAsyncFromCustomSynchronizationContext_PreCancelled_Unset_SynchronouslyCancels() 129 | { 130 | AsyncContext.Run(() => 131 | { 132 | var are = new AsyncAutoResetEvent(false); 133 | var token = new CancellationToken(true); 134 | 135 | var task = are.WaitAsync(token); 136 | 137 | Assert.IsTrue(task.IsCompleted); 138 | Assert.IsTrue(task.IsCanceled); 139 | Assert.IsFalse(task.IsFaulted); 140 | }); 141 | } 142 | #endif 143 | 144 | [Fact] 145 | public async Task WaitAsync_Cancelled_ThrowsException() 146 | { 147 | var are = new AsyncAutoResetEvent(); 148 | var cts = new CancellationTokenSource(); 149 | cts.Cancel(); 150 | var task = are.WaitAsync(cts.Token); 151 | await AsyncAssert.ThrowsAsync(task); 152 | } 153 | 154 | [Fact] 155 | public void Id_IsNotZero() 156 | { 157 | var are = new AsyncAutoResetEvent(); 158 | Assert.NotEqual(0, are.Id); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncConditionVariableUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | using Nito.AsyncEx.Testing; 9 | 10 | namespace UnitTests 11 | { 12 | public class AsyncConditionVariableUnitTests 13 | { 14 | [Fact] 15 | public async Task WaitAsync_WithoutNotify_IsNotCompleted() 16 | { 17 | var mutex = new AsyncLock(); 18 | var cv = new AsyncConditionVariable(mutex); 19 | 20 | await mutex.LockAsync(); 21 | var task = cv.WaitAsync(); 22 | 23 | await AsyncAssert.NeverCompletesAsync(task); 24 | } 25 | 26 | [Fact] 27 | public async Task WaitAsync_Notified_IsCompleted() 28 | { 29 | var mutex = new AsyncLock(); 30 | var cv = new AsyncConditionVariable(mutex); 31 | await mutex.LockAsync(); 32 | var task = cv.WaitAsync(); 33 | 34 | await Task.Run(async () => 35 | { 36 | using (await mutex.LockAsync()) 37 | { 38 | cv.Notify(); 39 | } 40 | }); 41 | await task; 42 | } 43 | 44 | [Fact] 45 | public async Task WaitAsync_AfterNotify_IsNotCompleted() 46 | { 47 | var mutex = new AsyncLock(); 48 | var cv = new AsyncConditionVariable(mutex); 49 | await Task.Run(async () => 50 | { 51 | using (await mutex.LockAsync()) 52 | { 53 | cv.Notify(); 54 | } 55 | }); 56 | 57 | await mutex.LockAsync(); 58 | var task = cv.WaitAsync(); 59 | 60 | await AsyncAssert.NeverCompletesAsync(task); 61 | } 62 | 63 | [Fact] 64 | public async Task MultipleWaits_NotifyAll_AllAreCompleted() 65 | { 66 | var mutex = new AsyncLock(); 67 | var cv = new AsyncConditionVariable(mutex); 68 | var key1 = await mutex.LockAsync(); 69 | var task1 = cv.WaitAsync(); 70 | var __ = task1.ContinueWith(_ => key1.Dispose()); 71 | var key2 = await mutex.LockAsync(); 72 | var task2 = cv.WaitAsync(); 73 | var ___ = task2.ContinueWith(_ => key2.Dispose()); 74 | 75 | await Task.Run(async () => 76 | { 77 | using (await mutex.LockAsync()) 78 | { 79 | cv.NotifyAll(); 80 | } 81 | }); 82 | 83 | await task1; 84 | await task2; 85 | } 86 | 87 | [Fact] 88 | public async Task MultipleWaits_Notify_OneIsCompleted() 89 | { 90 | var mutex = new AsyncLock(); 91 | var cv = new AsyncConditionVariable(mutex); 92 | var key = await mutex.LockAsync(); 93 | var task1 = cv.WaitAsync(); 94 | var __ = task1.ContinueWith(_ => key.Dispose()); 95 | await mutex.LockAsync(); 96 | var task2 = cv.WaitAsync(); 97 | 98 | await Task.Run(async () => 99 | { 100 | using (await mutex.LockAsync()) 101 | { 102 | cv.Notify(); 103 | } 104 | }); 105 | 106 | await task1; 107 | await AsyncAssert.NeverCompletesAsync(task2); 108 | } 109 | 110 | [Fact] 111 | public void Id_IsNotZero() 112 | { 113 | var mutex = new AsyncLock(); 114 | var cv = new AsyncConditionVariable(mutex); 115 | Assert.NotEqual(0, cv.Id); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncCountdownEventUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Nito.AsyncEx; 2 | using Nito.AsyncEx.Testing; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace UnitTests 10 | { 11 | public class AsyncCountdownEventUnitTests 12 | { 13 | [Fact] 14 | public async Task WaitAsync_Unset_IsNotCompleted() 15 | { 16 | var ce = new AsyncCountdownEvent(1); 17 | var task = ce.WaitAsync(); 18 | 19 | Assert.Equal(1, ce.CurrentCount); 20 | Assert.False(task.IsCompleted); 21 | 22 | ce.Signal(); 23 | await task; 24 | } 25 | 26 | [Fact] 27 | public void WaitAsync_Set_IsCompleted() 28 | { 29 | var ce = new AsyncCountdownEvent(0); 30 | var task = ce.WaitAsync(); 31 | 32 | Assert.Equal(0, ce.CurrentCount); 33 | Assert.True(task.IsCompleted); 34 | } 35 | 36 | [Fact] 37 | public async Task AddCount_IncrementsCount() 38 | { 39 | var ce = new AsyncCountdownEvent(1); 40 | var task = ce.WaitAsync(); 41 | Assert.Equal(1, ce.CurrentCount); 42 | Assert.False(task.IsCompleted); 43 | 44 | ce.AddCount(); 45 | 46 | Assert.Equal(2, ce.CurrentCount); 47 | Assert.False(task.IsCompleted); 48 | 49 | ce.Signal(2); 50 | await task; 51 | } 52 | 53 | [Fact] 54 | public async Task Signal_Nonzero_IsNotCompleted() 55 | { 56 | var ce = new AsyncCountdownEvent(2); 57 | var task = ce.WaitAsync(); 58 | Assert.False(task.IsCompleted); 59 | 60 | ce.Signal(); 61 | 62 | Assert.Equal(1, ce.CurrentCount); 63 | Assert.False(task.IsCompleted); 64 | 65 | ce.Signal(); 66 | await task; 67 | } 68 | 69 | [Fact] 70 | public void Signal_Zero_SynchronouslyCompletesWaitTask() 71 | { 72 | var ce = new AsyncCountdownEvent(1); 73 | var task = ce.WaitAsync(); 74 | Assert.False(task.IsCompleted); 75 | 76 | ce.Signal(); 77 | 78 | Assert.Equal(0, ce.CurrentCount); 79 | Assert.True(task.IsCompleted); 80 | } 81 | 82 | [Fact] 83 | public async Task Signal_AfterSet_CountsNegativeAndResetsTask() 84 | { 85 | var ce = new AsyncCountdownEvent(0); 86 | var originalTask = ce.WaitAsync(); 87 | 88 | ce.Signal(); 89 | 90 | var newTask = ce.WaitAsync(); 91 | Assert.Equal(-1, ce.CurrentCount); 92 | Assert.NotSame(originalTask, newTask); 93 | 94 | ce.AddCount(); 95 | await newTask; 96 | } 97 | 98 | [Fact] 99 | public async Task AddCount_AfterSet_CountsPositiveAndResetsTask() 100 | { 101 | var ce = new AsyncCountdownEvent(0); 102 | var originalTask = ce.WaitAsync(); 103 | 104 | ce.AddCount(); 105 | var newTask = ce.WaitAsync(); 106 | 107 | Assert.Equal(1, ce.CurrentCount); 108 | Assert.NotSame(originalTask, newTask); 109 | 110 | ce.Signal(); 111 | await newTask; 112 | } 113 | 114 | [Fact] 115 | public async Task Signal_PastZero_PulsesTask() 116 | { 117 | var ce = new AsyncCountdownEvent(1); 118 | var originalTask = ce.WaitAsync(); 119 | 120 | ce.Signal(2); 121 | await originalTask; 122 | var newTask = ce.WaitAsync(); 123 | 124 | Assert.Equal(-1, ce.CurrentCount); 125 | Assert.NotSame(originalTask, newTask); 126 | 127 | ce.AddCount(); 128 | await newTask; 129 | } 130 | 131 | [Fact] 132 | public async Task AddCount_PastZero_PulsesTask() 133 | { 134 | var ce = new AsyncCountdownEvent(-1); 135 | var originalTask = ce.WaitAsync(); 136 | 137 | ce.AddCount(2); 138 | await originalTask; 139 | var newTask = ce.WaitAsync(); 140 | 141 | Assert.Equal(1, ce.CurrentCount); 142 | Assert.NotSame(originalTask, newTask); 143 | 144 | ce.Signal(); 145 | await newTask; 146 | } 147 | 148 | [Fact] 149 | public void AddCount_Overflow_ThrowsException() 150 | { 151 | var ce = new AsyncCountdownEvent(long.MaxValue); 152 | AsyncAssert.Throws(() => ce.AddCount()); 153 | } 154 | 155 | [Fact] 156 | public void Signal_Underflow_ThrowsException() 157 | { 158 | var ce = new AsyncCountdownEvent(long.MinValue); 159 | AsyncAssert.Throws(() => ce.Signal()); 160 | } 161 | 162 | [Fact] 163 | public void Id_IsNotZero() 164 | { 165 | var ce = new AsyncCountdownEvent(0); 166 | Assert.NotEqual(0, ce.Id); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncEx.Coordination.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net461 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncLazyUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | using Nito.AsyncEx.Testing; 9 | 10 | #pragma warning disable CS0162 11 | 12 | 13 | namespace UnitTests 14 | { 15 | public class AsyncLazyUnitTests 16 | { 17 | [Fact] 18 | public void AsyncLazy_NeverAwaited_DoesNotCallFunc() 19 | { 20 | Func> func = () => 21 | { 22 | throw new Exception(); 23 | return Task.FromResult(13); 24 | }; 25 | 26 | var lazy = new AsyncLazy(func); 27 | } 28 | 29 | [Fact] 30 | public async Task AsyncLazy_WithCallDirectFlag_CallsFuncDirectly() 31 | { 32 | var testThread = Thread.CurrentThread.ManagedThreadId; 33 | var funcThread = testThread + 1; 34 | Func> func = () => 35 | { 36 | funcThread = Thread.CurrentThread.ManagedThreadId; 37 | return Task.FromResult(13); 38 | }; 39 | var lazy = new AsyncLazy(func, AsyncLazyFlags.ExecuteOnCallingThread); 40 | 41 | await lazy; 42 | 43 | Assert.Equal(testThread, funcThread); 44 | } 45 | 46 | [Fact] 47 | public async Task AsyncLazy_ByDefault_CallsFuncOnThreadPool() 48 | { 49 | var testThread = Thread.CurrentThread.ManagedThreadId; 50 | var funcThread = testThread; 51 | Func> func = () => 52 | { 53 | funcThread = Thread.CurrentThread.ManagedThreadId; 54 | return Task.FromResult(13); 55 | }; 56 | var lazy = new AsyncLazy(func); 57 | 58 | await lazy; 59 | 60 | Assert.NotEqual(testThread, funcThread); 61 | } 62 | 63 | [Fact] 64 | public async Task AsyncLazy_Start_CallsFunc() 65 | { 66 | var tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 67 | Func> func = () => 68 | { 69 | tcs.SetResult(null); 70 | return Task.FromResult(13); 71 | }; 72 | var lazy = new AsyncLazy(func); 73 | 74 | lazy.Start(); 75 | await tcs.Task; 76 | } 77 | 78 | [Fact] 79 | public async Task AsyncLazy_Await_ReturnsFuncValue() 80 | { 81 | Func> func = async () => 82 | { 83 | await Task.Yield(); 84 | return 13; 85 | }; 86 | var lazy = new AsyncLazy(func); 87 | 88 | var result = await lazy; 89 | Assert.Equal(13, result); 90 | } 91 | 92 | [Fact] 93 | public async Task AsyncLazy_MultipleAwaiters_OnlyInvokeFuncOnce() 94 | { 95 | int invokeCount = 0; 96 | var tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 97 | Func> func = async () => 98 | { 99 | Interlocked.Increment(ref invokeCount); 100 | await tcs.Task; 101 | return 13; 102 | }; 103 | var lazy = new AsyncLazy(func); 104 | 105 | var task1 = Task.Run(async () => await lazy); 106 | var task2 = Task.Run(async () => await lazy); 107 | 108 | Assert.False(task1.IsCompleted); 109 | Assert.False(task2.IsCompleted); 110 | tcs.SetResult(null); 111 | var results = await Task.WhenAll(task1, task2); 112 | Assert.True(results.SequenceEqual(new[] { 13, 13 })); 113 | Assert.Equal(1, invokeCount); 114 | } 115 | 116 | [Fact] 117 | public async Task AsyncLazy_FailureCachedByDefault() 118 | { 119 | int invokeCount = 0; 120 | Func> func = async () => 121 | { 122 | Interlocked.Increment(ref invokeCount); 123 | await Task.Yield(); 124 | if (invokeCount == 1) 125 | throw new InvalidOperationException("Not today, punk."); 126 | return 13; 127 | }; 128 | var lazy = new AsyncLazy(func); 129 | await AsyncAssert.ThrowsAsync(lazy.Task); 130 | 131 | await AsyncAssert.ThrowsAsync(lazy.Task); 132 | Assert.Equal(1, invokeCount); 133 | } 134 | 135 | [Fact] 136 | public async Task AsyncLazy_WithRetryOnFailure_DoesNotCacheFailure() 137 | { 138 | int invokeCount = 0; 139 | Func> func = async () => 140 | { 141 | Interlocked.Increment(ref invokeCount); 142 | await Task.Yield(); 143 | if (invokeCount == 1) 144 | throw new InvalidOperationException("Not today, punk."); 145 | return 13; 146 | }; 147 | var lazy = new AsyncLazy(func, AsyncLazyFlags.RetryOnFailure); 148 | await AsyncAssert.ThrowsAsync(lazy.Task); 149 | 150 | Assert.Equal(13, await lazy); 151 | Assert.Equal(2, invokeCount); 152 | } 153 | 154 | [Fact] 155 | public async Task AsyncLazy_WithRetryOnFailure_DoesNotRetryOnSuccess() 156 | { 157 | int invokeCount = 0; 158 | Func> func = async () => 159 | { 160 | Interlocked.Increment(ref invokeCount); 161 | await Task.Yield(); 162 | if (invokeCount == 1) 163 | throw new InvalidOperationException("Not today, punk."); 164 | return 13; 165 | }; 166 | var lazy = new AsyncLazy(func, AsyncLazyFlags.RetryOnFailure); 167 | await AsyncAssert.ThrowsAsync(lazy.Task); 168 | 169 | await lazy; 170 | await lazy; 171 | 172 | Assert.Equal(13, await lazy); 173 | Assert.Equal(2, invokeCount); 174 | } 175 | 176 | [Fact] 177 | public void Id_IsNotZero() 178 | { 179 | var lazy = new AsyncLazy(() => Task.FromResult(null)); 180 | Assert.NotEqual(0, lazy.Id); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncManualResetEventUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | using Nito.AsyncEx.Testing; 9 | 10 | namespace UnitTests 11 | { 12 | public class AsyncManualResetEventUnitTests 13 | { 14 | [Fact] 15 | public async Task WaitAsync_Unset_IsNotCompleted() 16 | { 17 | var mre = new AsyncManualResetEvent(); 18 | 19 | var task = mre.WaitAsync(); 20 | 21 | await AsyncAssert.NeverCompletesAsync(task); 22 | } 23 | 24 | [Fact] 25 | public async Task Wait_Unset_IsNotCompleted() 26 | { 27 | var mre = new AsyncManualResetEvent(); 28 | 29 | var task = Task.Run(() => mre.Wait()); 30 | 31 | await AsyncAssert.NeverCompletesAsync(task); 32 | } 33 | 34 | [Fact] 35 | public void WaitAsync_AfterSet_IsCompleted() 36 | { 37 | var mre = new AsyncManualResetEvent(); 38 | 39 | mre.Set(); 40 | var task = mre.WaitAsync(); 41 | 42 | Assert.True(task.IsCompleted); 43 | } 44 | 45 | [Fact] 46 | public void Wait_AfterSet_IsCompleted() 47 | { 48 | var mre = new AsyncManualResetEvent(); 49 | 50 | mre.Set(); 51 | mre.Wait(); 52 | } 53 | 54 | [Fact] 55 | public void WaitAsync_Set_IsCompleted() 56 | { 57 | var mre = new AsyncManualResetEvent(true); 58 | 59 | var task = mre.WaitAsync(); 60 | 61 | Assert.True(task.IsCompleted); 62 | } 63 | 64 | [Fact] 65 | public void Wait_Set_IsCompleted() 66 | { 67 | var mre = new AsyncManualResetEvent(true); 68 | 69 | mre.Wait(); 70 | } 71 | 72 | [Fact] 73 | public void MultipleWaitAsync_AfterSet_IsCompleted() 74 | { 75 | var mre = new AsyncManualResetEvent(); 76 | 77 | mre.Set(); 78 | var task1 = mre.WaitAsync(); 79 | var task2 = mre.WaitAsync(); 80 | 81 | Assert.True(task1.IsCompleted); 82 | Assert.True(task2.IsCompleted); 83 | } 84 | 85 | [Fact] 86 | public void MultipleWait_AfterSet_IsCompleted() 87 | { 88 | var mre = new AsyncManualResetEvent(); 89 | 90 | mre.Set(); 91 | mre.Wait(); 92 | mre.Wait(); 93 | } 94 | 95 | [Fact] 96 | public void MultipleWaitAsync_Set_IsCompleted() 97 | { 98 | var mre = new AsyncManualResetEvent(true); 99 | 100 | var task1 = mre.WaitAsync(); 101 | var task2 = mre.WaitAsync(); 102 | 103 | Assert.True(task1.IsCompleted); 104 | Assert.True(task2.IsCompleted); 105 | } 106 | 107 | [Fact] 108 | public void MultipleWait_Set_IsCompleted() 109 | { 110 | var mre = new AsyncManualResetEvent(true); 111 | 112 | mre.Wait(); 113 | mre.Wait(); 114 | } 115 | 116 | [Fact] 117 | public async Task WaitAsync_AfterReset_IsNotCompleted() 118 | { 119 | var mre = new AsyncManualResetEvent(); 120 | 121 | mre.Set(); 122 | mre.Reset(); 123 | var task = mre.WaitAsync(); 124 | 125 | await AsyncAssert.NeverCompletesAsync(task); 126 | } 127 | 128 | [Fact] 129 | public async Task Wait_AfterReset_IsNotCompleted() 130 | { 131 | var mre = new AsyncManualResetEvent(); 132 | 133 | mre.Set(); 134 | mre.Reset(); 135 | var task = Task.Run(() => mre.Wait()); 136 | 137 | await AsyncAssert.NeverCompletesAsync(task); 138 | } 139 | 140 | [Fact] 141 | public void Id_IsNotZero() 142 | { 143 | var mre = new AsyncManualResetEvent(); 144 | Assert.NotEqual(0, mre.Id); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncMonitorUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | 9 | namespace UnitTests 10 | { 11 | public class AsyncMonitorUnitTests 12 | { 13 | [Fact] 14 | public async Task Unlocked_PermitsLock() 15 | { 16 | var monitor = new AsyncMonitor(); 17 | 18 | var task = monitor.EnterAsync(); 19 | await task; 20 | } 21 | 22 | [Fact] 23 | public async Task Locked_PreventsLockUntilUnlocked() 24 | { 25 | var monitor = new AsyncMonitor(); 26 | var task1HasLock = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 27 | var task1Continue = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 28 | 29 | var task1 = Task.Run(async () => 30 | { 31 | using (await monitor.EnterAsync()) 32 | { 33 | task1HasLock.SetResult(null); 34 | await task1Continue.Task; 35 | } 36 | }); 37 | await task1HasLock.Task; 38 | 39 | var lockTask = monitor.EnterAsync().AsTask(); 40 | Assert.False(lockTask.IsCompleted); 41 | task1Continue.SetResult(null); 42 | await lockTask; 43 | } 44 | 45 | [Fact] 46 | public async Task Pulse_ReleasesOneWaiter() 47 | { 48 | var monitor = new AsyncMonitor(); 49 | int completed = 0; 50 | var task1Ready = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 51 | var task2Ready = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 52 | var task1 = Task.Run(async () => 53 | { 54 | using (await monitor.EnterAsync()) 55 | { 56 | var waitTask1 = monitor.WaitAsync(); 57 | task1Ready.SetResult(null); 58 | await waitTask1; 59 | Interlocked.Increment(ref completed); 60 | } 61 | }); 62 | await task1Ready.Task; 63 | var task2 = Task.Run(async () => 64 | { 65 | using (await monitor.EnterAsync()) 66 | { 67 | var waitTask2 = monitor.WaitAsync(); 68 | task2Ready.SetResult(null); 69 | await waitTask2; 70 | Interlocked.Increment(ref completed); 71 | } 72 | }); 73 | await task2Ready.Task; 74 | 75 | using (await monitor.EnterAsync()) 76 | { 77 | monitor.Pulse(); 78 | } 79 | await Task.WhenAny(task1, task2); 80 | var result = Interlocked.CompareExchange(ref completed, 0, 0); 81 | 82 | Assert.Equal(1, result); 83 | } 84 | 85 | [Fact] 86 | public async Task PulseAll_ReleasesAllWaiters() 87 | { 88 | var monitor = new AsyncMonitor(); 89 | int completed = 0; 90 | var task1Ready = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 91 | var task2Ready = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 92 | Task waitTask1 = null; 93 | var task1 = Task.Run(async () => 94 | { 95 | using (await monitor.EnterAsync()) 96 | { 97 | waitTask1 = monitor.WaitAsync(); 98 | task1Ready.SetResult(null); 99 | await waitTask1; 100 | Interlocked.Increment(ref completed); 101 | } 102 | }); 103 | await task1Ready.Task; 104 | Task waitTask2 = null; 105 | var task2 = Task.Run(async () => 106 | { 107 | using (await monitor.EnterAsync()) 108 | { 109 | waitTask2 = monitor.WaitAsync(); 110 | task2Ready.SetResult(null); 111 | await waitTask2; 112 | Interlocked.Increment(ref completed); 113 | } 114 | }); 115 | await task2Ready.Task; 116 | 117 | var lockTask3 = monitor.EnterAsync(); 118 | using (await lockTask3) 119 | { 120 | monitor.PulseAll(); 121 | } 122 | await Task.WhenAll(task1, task2); 123 | var result = Interlocked.CompareExchange(ref completed, 0, 0); 124 | 125 | Assert.Equal(2, result); 126 | } 127 | 128 | [Fact] 129 | public void Id_IsNotZero() 130 | { 131 | var monitor = new AsyncMonitor(); 132 | Assert.NotEqual(0, monitor.Id); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/AsyncEx.Coordination.UnitTests/AsyncSemaphoreUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | using Nito.AsyncEx.Testing; 9 | 10 | namespace UnitTests 11 | { 12 | public class AsyncSemaphoreUnitTests 13 | { 14 | [Fact] 15 | public async Task WaitAsync_NoSlotsAvailable_IsNotCompleted() 16 | { 17 | var semaphore = new AsyncSemaphore(0); 18 | Assert.Equal(0, semaphore.CurrentCount); 19 | var task = semaphore.WaitAsync(); 20 | Assert.Equal(0, semaphore.CurrentCount); 21 | await AsyncAssert.NeverCompletesAsync(task); 22 | } 23 | 24 | [Fact] 25 | public async Task WaitAsync_SlotAvailable_IsCompleted() 26 | { 27 | var semaphore = new AsyncSemaphore(1); 28 | Assert.Equal(1, semaphore.CurrentCount); 29 | var task1 = semaphore.WaitAsync(); 30 | Assert.Equal(0, semaphore.CurrentCount); 31 | Assert.True(task1.IsCompleted); 32 | var task2 = semaphore.WaitAsync(); 33 | Assert.Equal(0, semaphore.CurrentCount); 34 | await AsyncAssert.NeverCompletesAsync(task2); 35 | } 36 | 37 | [Fact] 38 | public void WaitAsync_PreCancelled_SlotAvailable_SucceedsSynchronously() 39 | { 40 | var semaphore = new AsyncSemaphore(1); 41 | Assert.Equal(1, semaphore.CurrentCount); 42 | var token = new CancellationToken(true); 43 | 44 | var task = semaphore.WaitAsync(token); 45 | 46 | Assert.Equal(0, semaphore.CurrentCount); 47 | Assert.True(task.IsCompleted); 48 | Assert.False(task.IsCanceled); 49 | Assert.False(task.IsFaulted); 50 | } 51 | 52 | [Fact] 53 | public void WaitAsync_PreCancelled_NoSlotAvailable_CancelsSynchronously() 54 | { 55 | var semaphore = new AsyncSemaphore(0); 56 | Assert.Equal(0, semaphore.CurrentCount); 57 | var token = new CancellationToken(true); 58 | 59 | var task = semaphore.WaitAsync(token); 60 | 61 | Assert.Equal(0, semaphore.CurrentCount); 62 | Assert.True(task.IsCompleted); 63 | Assert.True(task.IsCanceled); 64 | Assert.False(task.IsFaulted); 65 | } 66 | 67 | [Fact] 68 | public async Task WaitAsync_Cancelled_DoesNotTakeSlot() 69 | { 70 | var semaphore = new AsyncSemaphore(0); 71 | Assert.Equal(0, semaphore.CurrentCount); 72 | var cts = new CancellationTokenSource(); 73 | var task = semaphore.WaitAsync(cts.Token); 74 | Assert.Equal(0, semaphore.CurrentCount); 75 | Assert.False(task.IsCompleted); 76 | 77 | cts.Cancel(); 78 | 79 | try { await task; } 80 | catch (OperationCanceledException) { } 81 | semaphore.Release(); 82 | Assert.Equal(1, semaphore.CurrentCount); 83 | Assert.True(task.IsCanceled); 84 | } 85 | 86 | [Fact] 87 | public void Release_WithoutWaiters_IncrementsCount() 88 | { 89 | var semaphore = new AsyncSemaphore(0); 90 | Assert.Equal(0, semaphore.CurrentCount); 91 | semaphore.Release(); 92 | Assert.Equal(1, semaphore.CurrentCount); 93 | var task = semaphore.WaitAsync(); 94 | Assert.Equal(0, semaphore.CurrentCount); 95 | Assert.True(task.IsCompleted); 96 | } 97 | 98 | [Fact] 99 | public async Task Release_WithWaiters_ReleasesWaiters() 100 | { 101 | var semaphore = new AsyncSemaphore(0); 102 | Assert.Equal(0, semaphore.CurrentCount); 103 | var task = semaphore.WaitAsync(); 104 | Assert.Equal(0, semaphore.CurrentCount); 105 | Assert.False(task.IsCompleted); 106 | semaphore.Release(); 107 | Assert.Equal(0, semaphore.CurrentCount); 108 | await task; 109 | } 110 | 111 | [Fact] 112 | public void Release_Overflow_ThrowsException() 113 | { 114 | var semaphore = new AsyncSemaphore(long.MaxValue); 115 | Assert.Equal(long.MaxValue, semaphore.CurrentCount); 116 | AsyncAssert.Throws(() => semaphore.Release()); 117 | } 118 | 119 | [Fact] 120 | public void Release_ZeroSlots_HasNoEffect() 121 | { 122 | var semaphore = new AsyncSemaphore(1); 123 | Assert.Equal(1, semaphore.CurrentCount); 124 | semaphore.Release(0); 125 | Assert.Equal(1, semaphore.CurrentCount); 126 | } 127 | 128 | [Fact] 129 | public void Id_IsNotZero() 130 | { 131 | var semaphore = new AsyncSemaphore(0); 132 | Assert.NotEqual(0, semaphore.Id); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/AsyncEx.Interop.WaitHandles.UnitTests/AsyncEx.Interop.WaitHandles.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net461 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/AsyncEx.Interop.WaitHandles.UnitTests/WaitHandleInteropUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx.Interop; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics; 7 | using System.Diagnostics.CodeAnalysis; 8 | using Xunit; 9 | using Nito.AsyncEx.Testing; 10 | 11 | namespace UnitTests 12 | { 13 | public class WaitHandleInteropUnitTests 14 | { 15 | [Fact] 16 | public void FromWaitHandle_SignaledHandle_SynchronouslyCompletes() 17 | { 18 | var mre = new ManualResetEvent(true); 19 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre); 20 | Assert.True(task.IsCompleted); 21 | } 22 | 23 | [Fact] 24 | public void FromWaitHandle_SignaledHandleWithZeroTimeout_SynchronouslyCompletesWithTrueResult() 25 | { 26 | var mre = new ManualResetEvent(true); 27 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, TimeSpan.Zero); 28 | Assert.True(task.IsCompleted); 29 | Assert.True(task.Result); 30 | } 31 | 32 | [Fact] 33 | public void FromWaitHandle_UnsignaledHandleWithZeroTimeout_SynchronouslyCompletesWithFalseResult() 34 | { 35 | var mre = new ManualResetEvent(false); 36 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, TimeSpan.Zero); 37 | Assert.True(task.IsCompleted); 38 | Assert.False(task.Result); 39 | } 40 | 41 | [Fact] 42 | public void FromWaitHandle_SignaledHandleWithCanceledToken_SynchronouslyCompletes() 43 | { 44 | var mre = new ManualResetEvent(true); 45 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, new CancellationToken(true)); 46 | Assert.True(task.IsCompleted); 47 | } 48 | 49 | [Fact] 50 | public void FromWaitHandle_UnsignaledHandleWithCanceledToken_SynchronouslyCancels() 51 | { 52 | var mre = new ManualResetEvent(false); 53 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, new CancellationToken(true)); 54 | Assert.True(task.IsCompleted); 55 | Assert.True(task.IsCanceled); 56 | } 57 | 58 | [Fact] 59 | public void FromWaitHandle_SignaledHandleWithZeroTimeoutAndCanceledToken_SynchronouslyCompletesWithTrueResult() 60 | { 61 | var mre = new ManualResetEvent(true); 62 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, TimeSpan.Zero, new CancellationToken(true)); 63 | Assert.True(task.IsCompleted); 64 | Assert.True(task.Result); 65 | } 66 | 67 | [Fact] 68 | public void FromWaitHandle_UnsignaledHandleWithZeroTimeoutAndCanceledToken_SynchronouslyCompletesWithFalseResult() 69 | { 70 | var mre = new ManualResetEvent(false); 71 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, TimeSpan.Zero, new CancellationToken(true)); 72 | Assert.True(task.IsCompleted); 73 | Assert.False(task.Result); 74 | } 75 | 76 | [Fact] 77 | public async Task FromWaitHandle_HandleSignalled_Completes() 78 | { 79 | var mre = new ManualResetEvent(false); 80 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre); 81 | Assert.False(task.IsCompleted); 82 | mre.Set(); 83 | await task; 84 | } 85 | 86 | [Fact] 87 | public async Task FromWaitHandle_HandleSignalledBeforeTimeout_CompletesWithTrueResult() 88 | { 89 | var mre = new ManualResetEvent(false); 90 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, Timeout.InfiniteTimeSpan); 91 | Assert.False(task.IsCompleted); 92 | mre.Set(); 93 | var result = await task; 94 | Assert.True(result); 95 | } 96 | 97 | [Fact] 98 | public async Task FromWaitHandle_TimeoutBeforeHandleSignalled_CompletesWithFalseResult() 99 | { 100 | var mre = new ManualResetEvent(false); 101 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, TimeSpan.FromMilliseconds(10)); 102 | var result = await task; 103 | Assert.False(result); 104 | } 105 | 106 | [Fact] 107 | public async Task FromWaitHandle_HandleSignalledBeforeCanceled_CompletesSuccessfully() 108 | { 109 | var mre = new ManualResetEvent(false); 110 | var cts = new CancellationTokenSource(); 111 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, cts.Token); 112 | Assert.False(task.IsCompleted); 113 | mre.Set(); 114 | await task; 115 | } 116 | 117 | [Fact] 118 | public async Task FromWaitHandle_CanceledBeforeHandleSignalled_CompletesCanceled() 119 | { 120 | var mre = new ManualResetEvent(false); 121 | var cts = new CancellationTokenSource(); 122 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, cts.Token); 123 | Assert.False(task.IsCompleted); 124 | cts.Cancel(); 125 | await AsyncAssert.CancelsAsync(task); 126 | } 127 | 128 | [Fact] 129 | public async Task FromWaitHandle_HandleSignalledBeforeTimeoutOrCanceled_CompletesWithTrueResult() 130 | { 131 | var mre = new ManualResetEvent(false); 132 | var cts = new CancellationTokenSource(); 133 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, Timeout.InfiniteTimeSpan, cts.Token); 134 | Assert.False(task.IsCompleted); 135 | mre.Set(); 136 | var result = await task; 137 | Assert.True(result); 138 | } 139 | 140 | [Fact] 141 | public async Task FromWaitHandle_TimeoutBeforeHandleSignalledOrCanceled_CompletesWithFalseResult() 142 | { 143 | var mre = new ManualResetEvent(false); 144 | var cts = new CancellationTokenSource(); 145 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, TimeSpan.FromMilliseconds(10), cts.Token); 146 | var result = await task; 147 | Assert.False(result); 148 | } 149 | 150 | [Fact] 151 | public async Task FromWaitHandle_CanceledBeforeTimeoutOrHandleSignalled_CompletesCanceled() 152 | { 153 | var mre = new ManualResetEvent(false); 154 | var cts = new CancellationTokenSource(); 155 | var task = WaitHandleAsyncFactory.FromWaitHandle(mre, Timeout.InfiniteTimeSpan, cts.Token); 156 | Assert.False(task.IsCompleted); 157 | cts.Cancel(); 158 | await AsyncAssert.CancelsAsync(task); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/AsyncEx.Oop.UnitTests/AsyncEx.Oop.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net461 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/AsyncEx.Oop.UnitTests/DeferralManagerUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using Xunit; 5 | using Nito.AsyncEx.Testing; 6 | 7 | namespace UnitTests 8 | { 9 | public class DeferralManagerUnitTests 10 | { 11 | [Fact] 12 | public void NoDeferrals_IsCompleted() 13 | { 14 | var dm = new DeferralManager(); 15 | var task = dm.WaitForDeferralsAsync(); 16 | Assert.True(task.IsCompleted); 17 | } 18 | 19 | [Fact] 20 | public async Task IncompleteDeferral_PreventsCompletion() 21 | { 22 | var dm = new DeferralManager(); 23 | var deferral = dm.DeferralSource.GetDeferral(); 24 | await AsyncAssert.NeverCompletesAsync(dm.WaitForDeferralsAsync()); 25 | } 26 | 27 | [Fact] 28 | public async Task DeferralCompleted_Completes() 29 | { 30 | var dm = new DeferralManager(); 31 | var deferral = dm.DeferralSource.GetDeferral(); 32 | var task = dm.WaitForDeferralsAsync(); 33 | Assert.False(task.IsCompleted); 34 | deferral.Dispose(); 35 | await task; 36 | } 37 | 38 | [Fact] 39 | public async Task MultipleDeferralsWithOneIncomplete_PreventsCompletion() 40 | { 41 | var dm = new DeferralManager(); 42 | var deferral1 = dm.DeferralSource.GetDeferral(); 43 | var deferral2 = dm.DeferralSource.GetDeferral(); 44 | var task = dm.WaitForDeferralsAsync(); 45 | deferral1.Dispose(); 46 | await AsyncAssert.NeverCompletesAsync(task); 47 | } 48 | 49 | [Fact] 50 | public async Task TwoDeferralsWithOneCompletedTwice_PreventsCompletion() 51 | { 52 | var dm = new DeferralManager(); 53 | var deferral1 = dm.DeferralSource.GetDeferral(); 54 | var deferral2 = dm.DeferralSource.GetDeferral(); 55 | var task = dm.WaitForDeferralsAsync(); 56 | deferral1.Dispose(); 57 | deferral1.Dispose(); 58 | await AsyncAssert.NeverCompletesAsync(task); 59 | } 60 | 61 | [Fact] 62 | public async Task MultipleDeferralsWithAllCompleted_Completes() 63 | { 64 | var dm = new DeferralManager(); 65 | var deferral1 = dm.DeferralSource.GetDeferral(); 66 | var deferral2 = dm.DeferralSource.GetDeferral(); 67 | var task = dm.WaitForDeferralsAsync(); 68 | deferral1.Dispose(); 69 | deferral2.Dispose(); 70 | await task; 71 | } 72 | 73 | [Fact] 74 | public async Task CompletedDeferralFollowedByIncompleteDeferral_PreventsCompletion() 75 | { 76 | var dm = new DeferralManager(); 77 | dm.DeferralSource.GetDeferral().Dispose(); 78 | var deferral = dm.DeferralSource.GetDeferral(); 79 | var task = dm.WaitForDeferralsAsync(); 80 | await AsyncAssert.NeverCompletesAsync(task); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/AsyncEx.Tasks.UnitTests/AsyncEx.Tasks.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net461 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/AsyncEx.Tasks.UnitTests/CancellationTokenTaskSourceUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using Nito.AsyncEx.Testing; 7 | using Xunit; 8 | 9 | namespace UnitTests 10 | { 11 | public class CancellationTokenTaskSourceUnitTests 12 | { 13 | [Fact] 14 | public void Constructor_AlreadyCanceledToken_TaskReturnsSynchronouslyCanceledTask() 15 | { 16 | var token = new CancellationToken(true); 17 | using (var source = new CancellationTokenTaskSource(token)) 18 | Assert.True(source.Task.IsCanceled); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/AsyncEx.Tasks.UnitTests/TaskCompletionSourceExtensionsUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using System.ComponentModel; 8 | using Xunit; 9 | using Nito.AsyncEx.Testing; 10 | 11 | namespace UnitTests 12 | { 13 | public class TaskCompletionSourceExtensionsUnitTests 14 | { 15 | [Fact] 16 | public async Task TryCompleteFromCompletedTaskTResult_PropagatesResult() 17 | { 18 | var tcs = new TaskCompletionSource(); 19 | tcs.TryCompleteFromCompletedTask(TaskConstants.Int32NegativeOne); 20 | var result = await tcs.Task; 21 | Assert.Equal(-1, result); 22 | } 23 | 24 | [Fact] 25 | public async Task TryCompleteFromCompletedTaskTResult_WithDifferentTResult_PropagatesResult() 26 | { 27 | var tcs = new TaskCompletionSource(); 28 | tcs.TryCompleteFromCompletedTask(TaskConstants.Int32NegativeOne); 29 | var result = await tcs.Task; 30 | Assert.Equal(-1, result); 31 | } 32 | 33 | [Fact] 34 | public async Task TryCompleteFromCompletedTaskTResult_PropagatesCancellation() 35 | { 36 | var tcs = new TaskCompletionSource(); 37 | tcs.TryCompleteFromCompletedTask(TaskConstants.Canceled); 38 | await AsyncAssert.ThrowsAsync(() => tcs.Task); 39 | } 40 | 41 | [Fact] 42 | public async Task TryCompleteFromCompletedTaskTResult_PropagatesException() 43 | { 44 | var source = new TaskCompletionSource(); 45 | source.TrySetException(new NotImplementedException()); 46 | 47 | var tcs = new TaskCompletionSource(); 48 | tcs.TryCompleteFromCompletedTask(source.Task); 49 | await AsyncAssert.ThrowsAsync(() => tcs.Task); 50 | } 51 | 52 | [Fact] 53 | public async Task TryCompleteFromCompletedTask_PropagatesResult() 54 | { 55 | var tcs = new TaskCompletionSource(); 56 | tcs.TryCompleteFromCompletedTask(TaskConstants.Completed, () => -1); 57 | var result = await tcs.Task; 58 | Assert.Equal(-1, result); 59 | } 60 | 61 | [Fact] 62 | public async Task TryCompleteFromCompletedTask_PropagatesCancellation() 63 | { 64 | var tcs = new TaskCompletionSource(); 65 | tcs.TryCompleteFromCompletedTask(TaskConstants.Canceled, () => -1); 66 | await AsyncAssert.ThrowsAsync(() => tcs.Task); 67 | } 68 | 69 | [Fact] 70 | public async Task TryCompleteFromCompletedTask_PropagatesException() 71 | { 72 | var tcs = new TaskCompletionSource(); 73 | tcs.TryCompleteFromCompletedTask(Task.FromException(new NotImplementedException()), () => -1); 74 | await AsyncAssert.ThrowsAsync(() => tcs.Task); 75 | } 76 | 77 | [Fact] 78 | public async Task CreateAsyncTaskSource_PermitsCompletingTask() 79 | { 80 | var tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource(); 81 | tcs.SetResult(null); 82 | 83 | await tcs.Task; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/AsyncEx.Tasks.UnitTests/TaskConstantsUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Nito.AsyncEx; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Diagnostics.CodeAnalysis; 7 | using Xunit; 8 | 9 | namespace UnitTests 10 | { 11 | public class TaskConstantsUnitTests 12 | { 13 | [Fact] 14 | public void BooleanTrue_IsCompletedWithValueOfTrue() 15 | { 16 | var task = TaskConstants.BooleanTrue; 17 | Assert.True(task.IsCompleted); 18 | Assert.True(task.Result); 19 | } 20 | 21 | [Fact] 22 | public void BooleanTrue_IsCached() 23 | { 24 | var task1 = TaskConstants.BooleanTrue; 25 | var task2 = TaskConstants.BooleanTrue; 26 | Assert.Same(task1, task2); 27 | } 28 | 29 | [Fact] 30 | public void BooleanFalse_IsCompletedWithValueOfFalse() 31 | { 32 | var task = TaskConstants.BooleanFalse; 33 | Assert.True(task.IsCompleted); 34 | Assert.False(task.Result); 35 | } 36 | 37 | [Fact] 38 | public void BooleanFalse_IsCached() 39 | { 40 | var task1 = TaskConstants.BooleanFalse; 41 | var task2 = TaskConstants.BooleanFalse; 42 | Assert.Same(task1, task2); 43 | } 44 | 45 | [Fact] 46 | public void Int32Zero_IsCompletedWithValueOfZero() 47 | { 48 | var task = TaskConstants.Int32Zero; 49 | Assert.True(task.IsCompleted); 50 | Assert.Equal(0, task.Result); 51 | } 52 | 53 | [Fact] 54 | public void Int32Zero_IsCached() 55 | { 56 | var task1 = TaskConstants.Int32Zero; 57 | var task2 = TaskConstants.Int32Zero; 58 | Assert.Same(task1, task2); 59 | } 60 | 61 | [Fact] 62 | public void Int32NegativeOne_IsCompletedWithValueOfNegativeOne() 63 | { 64 | var task = TaskConstants.Int32NegativeOne; 65 | Assert.True(task.IsCompleted); 66 | Assert.Equal(-1, task.Result); 67 | } 68 | 69 | [Fact] 70 | public void Int32NegativeOne_IsCached() 71 | { 72 | var task1 = TaskConstants.Int32NegativeOne; 73 | var task2 = TaskConstants.Int32NegativeOne; 74 | Assert.Same(task1, task2); 75 | } 76 | 77 | [Fact] 78 | public void Completed_IsCompleted() 79 | { 80 | var task = TaskConstants.Completed; 81 | Assert.True(task.IsCompleted); 82 | } 83 | 84 | [Fact] 85 | public void Completed_IsCached() 86 | { 87 | var task1 = TaskConstants.Completed; 88 | var task2 = TaskConstants.Completed; 89 | Assert.Same(task1, task2); 90 | } 91 | 92 | [Fact] 93 | public void Canceled_IsCanceled() 94 | { 95 | var task = TaskConstants.Canceled; 96 | Assert.True(task.IsCanceled); 97 | } 98 | 99 | [Fact] 100 | public void Canceled_IsCached() 101 | { 102 | var task1 = TaskConstants.Canceled; 103 | var task2 = TaskConstants.Canceled; 104 | Assert.Same(task1, task2); 105 | } 106 | 107 | [Fact] 108 | public void Default_ReferenceType_IsCompletedWithValueOfNull() 109 | { 110 | var task = TaskConstants.Default; 111 | Assert.True(task.IsCompleted); 112 | Assert.Null(task.Result); 113 | } 114 | 115 | [Fact] 116 | public void Default_ValueType_IsCompletedWithValueOfZero() 117 | { 118 | var task = TaskConstants.Default; 119 | Assert.True(task.IsCompleted); 120 | Assert.Equal(0, task.Result); 121 | } 122 | 123 | [Fact] 124 | public void Default_IsCached() 125 | { 126 | var task1 = TaskConstants.Default; 127 | var task2 = TaskConstants.Default; 128 | Assert.Same(task1, task2); 129 | } 130 | 131 | [Fact] 132 | public void CanceledOfT_IsCanceled() 133 | { 134 | var task = TaskConstants.Canceled; 135 | Assert.True(task.IsCanceled); 136 | } 137 | 138 | [Fact] 139 | public void CanceledOfT_IsCached() 140 | { 141 | var task1 = TaskConstants.Canceled; 142 | var task2 = TaskConstants.Canceled; 143 | Assert.Same(task1, task2); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | --------------------------------------------------------------------------------