├── src ├── TplTipsAndTricks │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Common │ │ └── Weather.cs │ ├── WithTimeout │ │ ├── TaskEx.cs │ │ └── Sample.cs │ ├── ForEachAsync │ │ ├── Sample.cs │ │ └── TaskEx.cs │ ├── ProcessTasksByCompletion │ │ ├── TaskCompletionSourceEx.cs │ │ ├── TaskEx.cs │ │ └── Sample.cs │ └── TplTipsAndTricks.csproj └── TplTipsAndTricks.sln ├── LICENSE ├── .gitattributes └── .gitignore /src/TplTipsAndTricks/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/TplTipsAndTricks.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TplTipsAndTricks", "TplTipsAndTricks\TplTipsAndTricks.csproj", "{53F3A33D-B688-40A7-96BB-B42DBDA618CF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {53F3A33D-B688-40A7-96BB-B42DBDA618CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {53F3A33D-B688-40A7-96BB-B42DBDA618CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {53F3A33D-B688-40A7-96BB-B42DBDA618CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {53F3A33D-B688-40A7-96BB-B42DBDA618CF}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sergey Teplyakov 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. 22 | 23 | -------------------------------------------------------------------------------- /src/TplTipsAndTricks/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TplTipsAndTricks")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TplTipsAndTricks")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ec62d831-f926-40f5-9d61-9003039163b2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/TplTipsAndTricks/Common/Weather.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace TplTipsAndTricks.Common 5 | { 6 | class Weather 7 | { 8 | public Weather(int temperatureCelcius) 9 | { 10 | TemperatureCelcius = temperatureCelcius; 11 | } 12 | 13 | public int TemperatureCelcius { get; private set; } 14 | 15 | public override string ToString() 16 | { 17 | return string.Format("Temp: {0}C", TemperatureCelcius); 18 | } 19 | } 20 | 21 | public class WeatherUnavailableException : Exception 22 | { 23 | public WeatherUnavailableException(string city) 24 | : base(string.Format("Fail to get the weather for '{0}'", city)) 25 | { 26 | } 27 | } 28 | 29 | internal static class WeatherService 30 | { 31 | public static Task GetWeatherAsync(string city) 32 | { 33 | return Task.Run( 34 | async () => 35 | { 36 | await Task.Yield(); 37 | Console.WriteLine("Starting getting the weather for '{0}'", city); 38 | 39 | // Faking the temerature by city name length:) 40 | 41 | // Each task should take random amount of time 42 | var interval = 1 + new Random(Guid.NewGuid().GetHashCode()).Next(7); 43 | Console.WriteLine("Sleeping for {0}sec", interval); 44 | 45 | await Task.Delay(TimeSpan.FromSeconds(interval)); 46 | 47 | var result = new Weather(city.Length); 48 | Console.WriteLine("Got the weather for '{0}'", city); 49 | return result; 50 | }); 51 | } 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/TplTipsAndTricks/WithTimeout/TaskEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using TplTipsAndTricks.ProcessTasksByCompletion; 4 | 5 | namespace TplTipsAndTricks.WithTimeout 6 | { 7 | public static class TaskEx 8 | { 9 | public static Task WithTimeout(this Task task, TimeSpan timeout) 10 | { 11 | var tcs = new TaskCompletionSource(); 12 | 13 | task.ContinueWith(t => 14 | { 15 | // This method call will guarantee observe tasks exception! 16 | tcs.FromTask(t); 17 | }, TaskContinuationOptions.ExecuteSynchronously); 18 | 19 | Task.Delay(timeout) 20 | .ContinueWith(t => tcs.TrySetCanceled(), 21 | TaskContinuationOptions.ExecuteSynchronously); 22 | 23 | return tcs.Task; 24 | } 25 | 26 | public static Task WithTimeout(this Task task, TimeSpan timeout) 27 | { 28 | var tcs = new TaskCompletionSource(); 29 | 30 | task.ContinueWith(t => 31 | { 32 | // This method call will guarantee observe tasks exception! 33 | tcs.FromTask(t); 34 | }, TaskContinuationOptions.ExecuteSynchronously); 35 | 36 | Task.Delay(timeout) 37 | .ContinueWith(t => tcs.TrySetCanceled(), 38 | TaskContinuationOptions.ExecuteSynchronously); 39 | 40 | return tcs.Task; 41 | } 42 | 43 | /// 44 | /// Naive implementation of the 'WithTimeout' idiom. 45 | /// 46 | /// 47 | /// If will fail after timeout is occurred, UnobservedException could occurr. 48 | /// 49 | internal static Task WithTimeoutNaive(this Task task, TimeSpan timeout) 50 | { 51 | var tcs = new TaskCompletionSource(); 52 | 53 | Task.WhenAny(task, Task.Delay(timeout)) 54 | .ContinueWith(t => 55 | { 56 | if (t == task) 57 | { 58 | tcs.FromTask(task); 59 | } 60 | else 61 | { 62 | tcs.TrySetCanceled(); 63 | } 64 | }); 65 | 66 | return tcs.Task; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /src/TplTipsAndTricks/ForEachAsync/Sample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Threading.Tasks; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using TplTipsAndTricks.Common; 9 | using TplTipsAndTricks.ProcessTasksByCompletion; 10 | 11 | namespace TplTipsAndTricks.ForEachAsync 12 | { 13 | [TestFixture] 14 | public class Sample 15 | { 16 | private Task GetWeatherForAsync(string city, bool couldFail = false) 17 | { 18 | Console.WriteLine("[{1}]: Getting the weather for '{0}'", city, 19 | DateTime.Now.ToLongTimeString()); 20 | 21 | if (couldFail && (city == "Seattle" || city == "New York")) 22 | { 23 | throw new WeatherUnavailableException(city); 24 | } 25 | 26 | return WeatherService.GetWeatherAsync(city); 27 | } 28 | 29 | [Test] 30 | public async Task ForEachAsync() 31 | { 32 | var cities = new List { "Moscow", "Seattle", "New York", "Kiev" }; 33 | 34 | var tasks = cities.ForEachAsync(async city => 35 | { 36 | return new { City = city, Weather = await GetWeatherForAsync(city) }; 37 | }, 2); 38 | 39 | foreach (var task in tasks) 40 | { 41 | var taskResult = await task; 42 | 43 | ProcessWeather(taskResult.City, taskResult.Weather); 44 | } 45 | } 46 | 47 | [Test] 48 | public async Task ProcessByCompletionWithFailues() 49 | { 50 | var cities = new List { "Moscow", "Seattle", "New York", "Kiev" }; 51 | 52 | var tasks = cities.ForEachAsync(async city => 53 | { 54 | return new { City = city, Weather = await GetWeatherForAsync(city, true) }; 55 | }, 2); 56 | 57 | foreach (var task in tasks.OrderByCompletion()) 58 | { 59 | try 60 | { 61 | var taskResult = await task; 62 | ProcessWeather(taskResult.City, taskResult.Weather); 63 | } 64 | catch (Exception exception) 65 | { 66 | Console.WriteLine("[{1}]: Failure - '{0}'", exception.Message, 67 | DateTime.Now.ToLongTimeString()); 68 | } 69 | } 70 | } 71 | 72 | private void ProcessWeather(string city, Weather weather) 73 | { 74 | Console.WriteLine("[{2}]: Processing weather for '{0}': '{1}'", city, weather, 75 | DateTime.Now.ToLongTimeString()); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/TplTipsAndTricks/ProcessTasksByCompletion/TaskCompletionSourceEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | using System.Threading.Tasks; 4 | 5 | namespace TplTipsAndTricks.ProcessTasksByCompletion 6 | { 7 | public static class TaskCompletionSourceEx 8 | { 9 | 10 | public static void FromTask(this TaskCompletionSource tcs, Task task) 11 | { 12 | tcs.FromTask(task, _ => task.Result); 13 | } 14 | 15 | public static void FromTask( 16 | this TaskCompletionSource tcs, Task task, Func resultSelector) 17 | { 18 | // add additional checks 19 | if (task.Status == TaskStatus.Faulted) 20 | { 21 | // Black magic detected: extracting first exception if its the only one! 22 | var ae = task.Exception; 23 | var targetException = ae.InnerExceptions.Count == 1 ? ae.InnerExceptions[0] : ae; 24 | tcs.TrySetException(targetException); 25 | } 26 | else if (task.Status == TaskStatus.Canceled) 27 | { 28 | tcs.TrySetCanceled(); 29 | } 30 | else if (task.Status == TaskStatus.RanToCompletion) 31 | { 32 | tcs.TrySetResult(resultSelector(task.Result)); 33 | } 34 | else 35 | { 36 | string message = string.Format("Task should be in one of the final states! Current state: '{0}'", 37 | task.Status); 38 | 39 | Contract.Assert(false, message); 40 | throw new InvalidOperationException(message); 41 | } 42 | } 43 | 44 | public static void FromTask( 45 | this TaskCompletionSource tcs, Task task) 46 | { 47 | // add additional checks 48 | if (task.Status == TaskStatus.Faulted) 49 | { 50 | // Black magic detected: extracting first exception if its the only one! 51 | var ae = task.Exception; 52 | var targetException = ae.InnerExceptions.Count == 1 ? ae.InnerExceptions[0] : ae; 53 | tcs.TrySetException(targetException); 54 | } 55 | else if (task.Status == TaskStatus.Canceled) 56 | { 57 | tcs.TrySetCanceled(); 58 | } 59 | else if (task.Status == TaskStatus.RanToCompletion) 60 | { 61 | tcs.TrySetResult(null); 62 | } 63 | else 64 | { 65 | string message = string.Format("Task should be in one of the final states! Current state: '{0}'", 66 | task.Status); 67 | 68 | Contract.Assert(false, message); 69 | throw new InvalidOperationException(message); 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/TplTipsAndTricks/TplTipsAndTricks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {53F3A33D-B688-40A7-96BB-B42DBDA618CF} 8 | Library 9 | Properties 10 | TplTipsAndTricks 11 | TplTipsAndTricks 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll 35 | 36 | 37 | 38 | 39 | ..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll 40 | 41 | 42 | ..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll 43 | 44 | 45 | ..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll 46 | 47 | 48 | ..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /src/TplTipsAndTricks/WithTimeout/Sample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | 7 | namespace TplTipsAndTricks.WithTimeout 8 | { 9 | [TestFixture] 10 | public class Sample 11 | { 12 | public static async Task FooAsync(CancellationToken token) 13 | { 14 | 15 | } 16 | 17 | [Test] 18 | public async Task UseTaskCancellationSourceForTimeout() 19 | { 20 | try 21 | { 22 | TimeSpan timeout = TimeSpan.FromSeconds(1); 23 | var cts = new CancellationTokenSource(timeout); 24 | 25 | await Task.Delay(TimeSpan.FromSeconds(5), cts.Token); 26 | } 27 | catch (TaskCanceledException) 28 | { 29 | Console.WriteLine("Task was successfully cancelled!"); 30 | } 31 | } 32 | 33 | [Test] 34 | public async Task UseWithTimeout() 35 | { 36 | try 37 | { 38 | await Task.Delay(TimeSpan.FromSeconds(5)) 39 | .WithTimeout(TimeSpan.FromSeconds(1)); 40 | } 41 | catch (TaskCanceledException) 42 | { 43 | Console.WriteLine("Task was successfully cancelled!"); 44 | } 45 | } 46 | 47 | private static async Task LongRunningThatFail(int timeout) 48 | { 49 | await Task.Delay(timeout); 50 | throw new Exception("Long running operation failed!"); 51 | } 52 | 53 | private static async Task AsyncMethodWithTimeout(bool useNaive) 54 | { 55 | const int timeout = 500; 56 | { 57 | var task = LongRunningThatFail(timeout); 58 | 59 | try 60 | { 61 | if (useNaive) 62 | { 63 | await task.WithTimeoutNaive(TimeSpan.FromMilliseconds(100)); 64 | } 65 | else 66 | { 67 | await task.WithTimeout(TimeSpan.FromMilliseconds(100)); 68 | } 69 | Assert.Fail("Should never get here! Previous line should always throw!"); 70 | } 71 | catch (TaskCanceledException) 72 | { 73 | Console.WriteLine("Expected timeout!"); 74 | } 75 | 76 | // Additional timeout. Just in case 77 | Thread.Sleep(400); 78 | } 79 | } 80 | 81 | [Test] 82 | public void NaiveImplementationShouldThrow() 83 | { 84 | TaskScheduler.UnobservedTaskException += (sender, args) => 85 | { 86 | Console.WriteLine("Unobserved exception: " + args.Exception); 87 | 88 | }; 89 | 90 | { 91 | var tsk = AsyncMethodWithTimeout(useNaive: true); 92 | tsk.Wait(); 93 | GC.Collect(); 94 | GC.WaitForPendingFinalizers(); 95 | GC.Collect(); 96 | Console.WriteLine("Done!"); 97 | } 98 | 99 | GC.Collect(); 100 | GC.WaitForPendingFinalizers(); 101 | GC.Collect(); 102 | Console.WriteLine("Done!"); 103 | } 104 | 105 | [Test] 106 | public void WithTimeoutShouldNotThrowUnobservedException() 107 | { 108 | TaskScheduler.UnobservedTaskException += (sender, args) => 109 | { 110 | Console.WriteLine("Unobserved exception: " + args.Exception); 111 | 112 | }; 113 | 114 | { 115 | var tsk = AsyncMethodWithTimeout(useNaive: false); 116 | tsk.Wait(); 117 | GC.Collect(); 118 | GC.WaitForPendingFinalizers(); 119 | GC.Collect(); 120 | Console.WriteLine("Done!"); 121 | } 122 | 123 | GC.Collect(); 124 | GC.WaitForPendingFinalizers(); 125 | GC.Collect(); 126 | Console.WriteLine("Done!"); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/TplTipsAndTricks/ForEachAsync/TaskEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.Contracts; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using TplTipsAndTricks.ProcessTasksByCompletion; 10 | 11 | namespace TplTipsAndTricks.ForEachAsync 12 | { 13 | public static class TaskEx 14 | { 15 | public static IEnumerable> ForEachAsync( 16 | this IEnumerable source, Func> selector, 17 | int degreeOfParallelism) 18 | { 19 | Contract.Requires(source != null); 20 | Contract.Requires(selector != null); 21 | 22 | // We need to know all the items in the source before starting tasks 23 | var tasks = source.ToList(); 24 | 25 | int completedTask = -1; 26 | 27 | // Creating an array of TaskCompletionSource that would holds 28 | // the results for each operations 29 | var taskCompletions = new TaskCompletionSource[tasks.Count]; 30 | for(int n = 0; n < taskCompletions.Length; n++) 31 | taskCompletions[n] = new TaskCompletionSource(); 32 | 33 | // Partitioner would do all grunt work for us and split 34 | // the source into appropriate number of chunks for parallel processing 35 | foreach (var partition in 36 | Partitioner.Create(tasks).GetPartitions(degreeOfParallelism)) 37 | { 38 | var p = partition; 39 | 40 | // Loosing sync context and starting asynchronous 41 | // computation for each partition 42 | Task.Run(async () => 43 | { 44 | while (p.MoveNext()) 45 | { 46 | var task = selector(p.Current); 47 | 48 | // Don't want to use empty catch . 49 | // This trick just swallows an exception 50 | await task.ContinueWith(_ => { }); 51 | 52 | int finishedTaskIndex = Interlocked.Increment(ref completedTask); 53 | taskCompletions[finishedTaskIndex].FromTask(task); 54 | } 55 | }); 56 | } 57 | 58 | return taskCompletions.Select(tcs => tcs.Task); 59 | } 60 | 61 | /// 62 | /// Implementation by Stephen Toub. Found at: http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx 63 | /// 64 | public static Task ForEachAsync(this IEnumerable source, int dop, Func body) 65 | { 66 | return Task.WhenAll( 67 | from partition in Partitioner.Create(source).GetPartitions(dop) 68 | select Task.Run(async delegate 69 | { 70 | using (partition) 71 | while (partition.MoveNext()) 72 | await body(partition.Current); 73 | })); 74 | } 75 | 76 | /// 77 | /// Implementation by Stephen Toub. Found at: http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx 78 | /// 79 | public static async Task ForEachAsyncWithExceptions(this IEnumerable source, int dop, Func body) 80 | { 81 | ConcurrentQueue exceptions = null; 82 | 83 | await Task.WhenAll( 84 | from partition in Partitioner.Create(source).GetPartitions(dop) 85 | select Task.Run(async delegate 86 | { 87 | using (partition) 88 | { 89 | while (partition.MoveNext()) 90 | { 91 | try 92 | { 93 | await body(partition.Current); 94 | } 95 | catch (Exception e) 96 | { 97 | LazyInitializer.EnsureInitialized(ref exceptions).Enqueue(e); 98 | } 99 | } 100 | } 101 | })); 102 | 103 | if (exceptions != null) 104 | { 105 | throw new AggregateException(exceptions); 106 | } 107 | } 108 | 109 | } 110 | } -------------------------------------------------------------------------------- /src/TplTipsAndTricks/ProcessTasksByCompletion/TaskEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace TplTipsAndTricks.ProcessTasksByCompletion 9 | { 10 | public static class TaskEx 11 | { 12 | public static Task FromTask( 13 | TResult result, Func> taskSelector) 14 | { 15 | Contract.Requires(taskSelector != null); 16 | 17 | var tcs = new TaskCompletionSource(); 18 | var task = taskSelector(result); 19 | 20 | task.ContinueWith(t => 21 | { 22 | tcs.FromTask(task, _ => result); 23 | }); 24 | 25 | return tcs.Task; 26 | } 27 | 28 | public static IEnumerable> OrderByCompletion( 29 | this IEnumerable sequence, Func> taskSelector) 30 | { 31 | Contract.Requires(sequence != null); 32 | Contract.Requires(taskSelector != null); 33 | 34 | var tasks = (from element in sequence 35 | let pair = new {Element = element, Task = taskSelector(element)} 36 | select FromTask(pair, p => p.Task)).ToList(); 37 | 38 | while (tasks.Count != 0) 39 | { 40 | var tcs = new TaskCompletionSource(); 41 | 42 | // Getting the first finished task 43 | Task.WhenAny(tasks).ContinueWith(tsk => 44 | { 45 | var finishedTask = tsk.Result; 46 | tasks.Remove(finishedTask); 47 | 48 | tcs.FromTask(finishedTask, arg => arg.Element); 49 | }); 50 | 51 | yield return tcs.Task; 52 | } 53 | } 54 | 55 | /// 56 | /// Method that transforms sequence of tasks into another sequence of tasks with strict 57 | /// completion order. I.e. first item in the resulting sequence would be finished first, 58 | /// second item - second etc. 59 | /// 60 | /// 61 | /// This method helps to analyze the results not based on the order of the tasks in 62 | /// the original sequence but in the order of completion. 63 | /// 64 | public static IEnumerable> OrderByCompletionNaive(this IEnumerable> taskSequence) 65 | { 66 | // Need to return task immediately, but it should be in complete state when the first task would be completed! 67 | Contract.Requires(taskSequence != null); 68 | 69 | var tasks = taskSequence.ToList(); 70 | 71 | while (tasks.Count != 0) 72 | { 73 | // We whould have additional closure for each iteration but in a task-based world 74 | // this should be fine! 75 | 76 | var tcs = new TaskCompletionSource(); 77 | 78 | // Getting the first finished task 79 | Task.WhenAny(tasks).ContinueWith((Task> tsk) => 80 | { 81 | tasks.Remove(tsk.Result); 82 | 83 | tcs.FromTask(tsk.Result); 84 | }); 85 | 86 | yield return tcs.Task; 87 | } 88 | } 89 | 90 | /// 91 | /// OrderByCompletionNaive has significant drawback - O(N^2) complexity and O(N^2) number of continuations. 92 | /// Current implemnetation should solve this issue. 93 | /// 94 | public static IEnumerable> OrderByCompletion(this IEnumerable> taskSequence) 95 | { 96 | // Need to return task immediately, but it should be in complete state when the first task would be completed! 97 | Contract.Requires(taskSequence != null); 98 | 99 | var tasks = taskSequence.ToList(); 100 | 101 | var taskCompletions = new TaskCompletionSource[tasks.Count]; 102 | 103 | int completedTask = -1; 104 | 105 | Action> continuation = tsk => 106 | { 107 | var finishedTask = Interlocked.Increment(ref completedTask); 108 | taskCompletions[finishedTask].FromTask(tsk); 109 | }; 110 | 111 | for (int i = 0; i < tasks.Count; i++) 112 | { 113 | taskCompletions[i] = new TaskCompletionSource(); 114 | tasks[i].ContinueWith(continuation); 115 | } 116 | 117 | return taskCompletions.Select(tcs => tcs.Task); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/TplTipsAndTricks/ProcessTasksByCompletion/Sample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using System.Reactive.Concurrency; 6 | using System.Reactive.Linq; 7 | using System.Reactive.Subjects; 8 | using System.Reactive.Threading.Tasks; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using NUnit.Framework; 12 | using TplTipsAndTricks.Common; 13 | 14 | namespace TplTipsAndTricks.ProcessTasksByCompletion 15 | { 16 | public static partial class ObservableExtensions 17 | { 18 | public static IObservable SelectMany( 19 | this IObservable source, 20 | Func> selector, 21 | int maxDegreeOfConcurrency) 22 | { 23 | return Observable.Create((observer, token) => 24 | { 25 | //var syncedObserver = Subject.(observer); 26 | var semaphore = new SemaphoreSlim(maxDegreeOfConcurrency); 27 | var subscription = source.Subscribe( 28 | async value => 29 | { 30 | try 31 | { 32 | semaphore.Wait(token); 33 | } 34 | catch (OperationCanceledException) 35 | { 36 | return; 37 | } 38 | catch (Exception ex) 39 | { 40 | observer.OnError(ex); 41 | return; 42 | } 43 | 44 | TResult result; 45 | try 46 | { 47 | result = await selector(value, token).ConfigureAwait(false); 48 | } 49 | catch (OperationCanceledException ex) 50 | { 51 | semaphore.Release(); 52 | if (ex.CancellationToken != token) 53 | { 54 | observer.OnError(ex); 55 | } 56 | 57 | return; 58 | } 59 | catch (Exception ex) 60 | { 61 | semaphore.Release(); 62 | observer.OnError(ex); 63 | 64 | return; 65 | } 66 | 67 | semaphore.Release(); 68 | observer.OnNext(result); 69 | }, 70 | observer.OnError, 71 | observer.OnCompleted); 72 | 73 | return Task.FromResult(subscription); 74 | }).Synchronize(); 75 | } 76 | } 77 | 78 | [TestFixture] 79 | public class Sample 80 | { 81 | private Task GetWeatherForAsync(string city) 82 | { 83 | Console.WriteLine("[{1}]: Getting the weather for '{0}'", city, 84 | DateTime.Now.ToLongTimeString()); 85 | return WeatherService.GetWeatherAsync(city); 86 | } 87 | 88 | [Test] 89 | public async Task ManualProcessByCompletion() 90 | { 91 | var cities = new List { "Moscow", "Seattle", "New York" }; 92 | var tasks = cities.Select(async city => 93 | { 94 | return new {City = city, Weather = await GetWeatherForAsync(city)}; 95 | }).ToList(); 96 | 97 | while (tasks.Count != 0) 98 | { 99 | var completedTask = await Task.WhenAny(tasks); 100 | 101 | tasks.Remove(completedTask); 102 | 103 | var result = completedTask.Result; 104 | 105 | ProcessWeather(result.City, result.Weather); 106 | } 107 | } 108 | 109 | [Test] 110 | public async Task ProcessByCompletionWithQueryComprehension() 111 | { 112 | var cities = new List { "Moscow", "Seattle", "New York" }; 113 | 114 | var tasks = 115 | from city in cities 116 | select new {City = city, WeatherTask = GetWeatherForAsync(city)}; 117 | 118 | foreach (var task in tasks.OrderByCompletion(t => t.WeatherTask)) 119 | { 120 | var taskResult = await task; 121 | 122 | // taskResult is an object of anonymous type with City and WeatherTask 123 | ProcessWeather(taskResult.City, taskResult.WeatherTask.Result); 124 | } 125 | } 126 | 127 | [Test] 128 | public async Task ProcessByCompletion() 129 | { 130 | var cities = new List { "Moscow", "Seattle", "New York" }; 131 | 132 | var tasks = cities.Select(async city => 133 | { 134 | return new {City = city, Weather = await GetWeatherForAsync(city)}; 135 | }); 136 | 137 | foreach (var task in tasks.OrderByCompletion()) 138 | { 139 | var taskResult = await task; 140 | 141 | // taskResult is an object of anonymous type with City and WeatherTask 142 | ProcessWeather(taskResult.City, taskResult.Weather); 143 | } 144 | } 145 | 146 | [Test] 147 | public void ProcessOneUsingRx() 148 | { 149 | var cities = new[] { "Moscow", "Seattle", "New York" }; 150 | var objs = cities.Select(async city => new 151 | { 152 | City = city, 153 | Weather = await GetWeatherForAsync(city) 154 | }).Select(task => task.ToObservable()).Merge().ToEnumerable(); 155 | 156 | foreach (var obj in objs) 157 | { 158 | ProcessWeather(obj.City, obj.Weather); 159 | } 160 | } 161 | 162 | [Test] 163 | public Task ProcessUsingRx() 164 | { 165 | var cities = new[] { "Moscow", "Seattle", "New York" }; 166 | return cities 167 | .ToObservable() 168 | .SelectMany( 169 | (city, cancellation) => GetWeatherForAsync(city), 170 | (city, weather) => new { City = city, Weather = weather }) 171 | //.ObserveOn(Scheduler.CurrentThread) //optional 172 | .ForEachAsync(result => ProcessWeather(result.City, result.Weather)); 173 | } 174 | 175 | [Test] 176 | public async Task ProcessOneByOneNaive() 177 | { 178 | var cities = new List { "Moscow", "Seattle", "New York" }; 179 | 180 | var tasks = 181 | from city in cities 182 | select new { City = city, WeatherTask = GetWeatherForAsync(city) }; 183 | 184 | foreach (var entry in tasks) 185 | { 186 | var wheather = await entry.WeatherTask; 187 | 188 | ProcessWeather(entry.City, wheather); 189 | } 190 | } 191 | 192 | private void ProcessWeather(string city, Weather weather) 193 | { 194 | Console.WriteLine("[{2}]: Processing weather for '{0}': '{1}'", city, weather, 195 | DateTime.Now.ToLongTimeString()); 196 | } 197 | } 198 | } --------------------------------------------------------------------------------