Threads and C#
authorgu.martinm@gmail.com <gu.martinm@gmail.com>
Sun, 18 May 2014 01:49:11 +0000 (03:49 +0200)
committergu.martinm@gmail.com <gu.martinm@gmail.com>
Sun, 18 May 2014 01:49:11 +0000 (03:49 +0200)
http://parallelpatterns.codeplex.com/
Chapter 2: Parallel Loops

Allgemeines/Threads/Threads.sln [new file with mode: 0644]
Allgemeines/Threads/Threads/Chapter2.cs [new file with mode: 0644]
Allgemeines/Threads/Threads/Program.cs [new file with mode: 0644]
Allgemeines/Threads/Threads/Properties/AssemblyInfo.cs [new file with mode: 0644]
Allgemeines/Threads/Threads/Threads.csproj [new file with mode: 0644]

diff --git a/Allgemeines/Threads/Threads.sln b/Allgemeines/Threads/Threads.sln
new file mode 100644 (file)
index 0000000..cc1d37d
--- /dev/null
@@ -0,0 +1,149 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Threads", "Threads\Threads.csproj", "{CED02136-FA8A-4E92-9BC6-3340DA89C3A4}"
+EndProject
+Global
+       GlobalSection(SolutionConfigurationPlatforms) = preSolution
+               Debug|x86 = Debug|x86
+               Release|x86 = Release|x86
+       EndGlobalSection
+       GlobalSection(ProjectConfigurationPlatforms) = postSolution
+               {CED02136-FA8A-4E92-9BC6-3340DA89C3A4}.Debug|x86.ActiveCfg = Debug|x86
+               {CED02136-FA8A-4E92-9BC6-3340DA89C3A4}.Debug|x86.Build.0 = Debug|x86
+               {CED02136-FA8A-4E92-9BC6-3340DA89C3A4}.Release|x86.ActiveCfg = Release|x86
+               {CED02136-FA8A-4E92-9BC6-3340DA89C3A4}.Release|x86.Build.0 = Release|x86
+       EndGlobalSection
+       GlobalSection(MonoDevelopProperties) = preSolution
+               StartupItem = Threads\Threads.csproj
+               Policies = $0
+               $0.DotNetNamingPolicy = $1
+               $1.DirectoryNamespaceAssociation = None
+               $1.ResourceNamePolicy = FileFormatDefault
+               $0.NameConventionPolicy = $2
+               $2.Rules = $3
+               $3.NamingRule = $4
+               $4.Name = Namespaces
+               $4.AffectedEntity = Namespace
+               $4.VisibilityMask = VisibilityMask
+               $4.NamingStyle = PascalCase
+               $4.IncludeInstanceMembers = True
+               $4.IncludeStaticEntities = True
+               $3.NamingRule = $5
+               $5.Name = Types
+               $5.AffectedEntity = Class, Struct, Enum, Delegate
+               $5.VisibilityMask = Public
+               $5.NamingStyle = PascalCase
+               $5.IncludeInstanceMembers = True
+               $5.IncludeStaticEntities = True
+               $3.NamingRule = $6
+               $6.Name = Interfaces
+               $6.RequiredPrefixes = $7
+               $7.String = I
+               $6.AffectedEntity = Interface
+               $6.VisibilityMask = Public
+               $6.NamingStyle = PascalCase
+               $6.IncludeInstanceMembers = True
+               $6.IncludeStaticEntities = True
+               $3.NamingRule = $8
+               $8.Name = Attributes
+               $8.RequiredSuffixes = $9
+               $9.String = Attribute
+               $8.AffectedEntity = CustomAttributes
+               $8.VisibilityMask = Public
+               $8.NamingStyle = PascalCase
+               $8.IncludeInstanceMembers = True
+               $8.IncludeStaticEntities = True
+               $3.NamingRule = $10
+               $10.Name = Event Arguments
+               $10.RequiredSuffixes = $11
+               $11.String = EventArgs
+               $10.AffectedEntity = CustomEventArgs
+               $10.VisibilityMask = Public
+               $10.NamingStyle = PascalCase
+               $10.IncludeInstanceMembers = True
+               $10.IncludeStaticEntities = True
+               $3.NamingRule = $12
+               $12.Name = Exceptions
+               $12.RequiredSuffixes = $13
+               $13.String = Exception
+               $12.AffectedEntity = CustomExceptions
+               $12.VisibilityMask = VisibilityMask
+               $12.NamingStyle = PascalCase
+               $12.IncludeInstanceMembers = True
+               $12.IncludeStaticEntities = True
+               $3.NamingRule = $14
+               $14.Name = Methods
+               $14.AffectedEntity = Methods
+               $14.VisibilityMask = Protected, Public
+               $14.NamingStyle = PascalCase
+               $14.IncludeInstanceMembers = True
+               $14.IncludeStaticEntities = True
+               $3.NamingRule = $15
+               $15.Name = Static Readonly Fields
+               $15.AffectedEntity = ReadonlyField
+               $15.VisibilityMask = Protected, Public
+               $15.NamingStyle = PascalCase
+               $15.IncludeInstanceMembers = False
+               $15.IncludeStaticEntities = True
+               $3.NamingRule = $16
+               $16.Name = Fields
+               $16.AffectedEntity = Field
+               $16.VisibilityMask = Protected, Public
+               $16.NamingStyle = PascalCase
+               $16.IncludeInstanceMembers = True
+               $16.IncludeStaticEntities = True
+               $3.NamingRule = $17
+               $17.Name = ReadOnly Fields
+               $17.AffectedEntity = ReadonlyField
+               $17.VisibilityMask = Protected, Public
+               $17.NamingStyle = PascalCase
+               $17.IncludeInstanceMembers = True
+               $17.IncludeStaticEntities = False
+               $3.NamingRule = $18
+               $18.Name = Constant Fields
+               $18.AffectedEntity = ConstantField
+               $18.VisibilityMask = Protected, Public
+               $18.NamingStyle = PascalCase
+               $18.IncludeInstanceMembers = True
+               $18.IncludeStaticEntities = True
+               $3.NamingRule = $19
+               $19.Name = Properties
+               $19.AffectedEntity = Property
+               $19.VisibilityMask = Protected, Public
+               $19.NamingStyle = PascalCase
+               $19.IncludeInstanceMembers = True
+               $19.IncludeStaticEntities = True
+               $3.NamingRule = $20
+               $20.Name = Events
+               $20.AffectedEntity = Event
+               $20.VisibilityMask = Protected, Public
+               $20.NamingStyle = PascalCase
+               $20.IncludeInstanceMembers = True
+               $20.IncludeStaticEntities = True
+               $3.NamingRule = $21
+               $21.Name = Enum Members
+               $21.AffectedEntity = EnumMember
+               $21.VisibilityMask = VisibilityMask
+               $21.NamingStyle = PascalCase
+               $21.IncludeInstanceMembers = True
+               $21.IncludeStaticEntities = True
+               $3.NamingRule = $22
+               $22.Name = Parameters
+               $22.AffectedEntity = Parameter
+               $22.VisibilityMask = VisibilityMask
+               $22.NamingStyle = CamelCase
+               $22.IncludeInstanceMembers = True
+               $22.IncludeStaticEntities = True
+               $3.NamingRule = $23
+               $23.Name = Type Parameters
+               $23.RequiredPrefixes = $24
+               $24.String = T
+               $23.AffectedEntity = TypeParameter
+               $23.VisibilityMask = VisibilityMask
+               $23.NamingStyle = PascalCase
+               $23.IncludeInstanceMembers = True
+               $23.IncludeStaticEntities = True
+               description = @Learning about threads and C#\nSee: http://parallelpatterns.codeplex.com/
+       EndGlobalSection
+EndGlobal
diff --git a/Allgemeines/Threads/Threads/Chapter2.cs b/Allgemeines/Threads/Threads/Chapter2.cs
new file mode 100644 (file)
index 0000000..fc50172
--- /dev/null
@@ -0,0 +1,319 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Linq;
+using System.Collections.Concurrent;
+
+namespace Threads
+{
+    /// <summary>
+    /// 
+    /// Chapter2.
+    /// Parallel Loops
+    /// 
+    /// Taken from http://msdn.microsoft.com/en-us/library/ff963552.aspx
+    /// 
+    /// </summary>
+    public class Chapter2
+    {
+        public static void Test()
+        {
+            int n = 10;
+
+            Console.WriteLine("For");
+            for (int i = 0; i < n; i++)
+            {
+                Console.WriteLine("For {0}", i);
+                Thread.Sleep(500);
+            }
+
+
+            Console.WriteLine("Parallel.For");
+            Parallel.For(0, n, i =>
+            {
+                Console.WriteLine("Parallel.For {0}", i);
+                Thread.Sleep(500);
+            });
+
+
+            Console.WriteLine("Parallel.ForEach");
+            List<string> listOfStrings = new List<string>();
+            listOfStrings.Add("one");
+            listOfStrings.Add("two");
+            listOfStrings.Add("three");
+            listOfStrings.Add("four");
+            Parallel.ForEach(listOfStrings, stringnumber =>
+            {
+                Console.WriteLine("Parallel.ForEach {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+
+
+            Console.WriteLine("LINQ");
+            var anonymousFromListOfStrings = from stringnumber in listOfStrings select new { Original = stringnumber };
+            // Use ForAll if you need to iterate over a PLINQ result. Don't use ForEach in this case. :(
+            // I should have used: anonymousFromListOfStrings.ForAll  See example at the end of this class :)
+            Parallel.ForEach(anonymousFromListOfStrings, stringnumber =>
+            {
+                Console.WriteLine("LINQ {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+            var stringsFromListOfString = listOfStrings.Select(stringnumber => new String(stringnumber.ToCharArray()));
+            // Use ForAll if you need to iterate over a PLINQ result. Don't use ForEach in this case. :(
+            // I should have used: stringsFromListOfString.ForAll  See example at the end of this class :)
+            Parallel.ForEach(stringsFromListOfString, stringnumber =>
+            {
+                Console.WriteLine("LINQ {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+
+
+            Console.WriteLine("PLINQ");
+            anonymousFromListOfStrings = from stringnumber in listOfStrings.AsParallel() select new { Original = stringnumber };
+            Parallel.ForEach(anonymousFromListOfStrings, stringnumber =>
+            {
+                Console.WriteLine("PLINQ {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+            stringsFromListOfString = listOfStrings.AsParallel().Select(stringnumber => new String(stringnumber.ToCharArray()));
+            Parallel.ForEach(stringsFromListOfString, stringnumber =>
+            {
+                Console.WriteLine("PLINQ {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+
+
+            Console.WriteLine("PLINQ, ForAll");
+            listOfStrings.AsParallel().ForAll(stringnumber =>
+            {
+                Console.WriteLine("PLINQ, ForAll {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+
+
+            Console.WriteLine("Parallel Break");
+            // During the processing of a call to the Break method, iterations with an index
+            // value less than the current index will be allowed to start (if they have not already started),
+            // but iterations with an index value greater than the current index will not be started.
+            // This ensures that all iterations below the break point will complete.
+            n = 20;
+            var loopResult = Parallel.For(0, n, (i, loopState) =>
+            {
+                // What should I use? LowestBreakIteration or ShouldExitCurrentIteration? :(
+                if (loopState.LowestBreakIteration.HasValue || loopState.ShouldExitCurrentIteration)
+                {
+                    Console.WriteLine("Parallel Break Check State {0}", i);
+                    loopState.Break();
+                    return;
+                }
+
+                Console.WriteLine("Parallel Break {0}", i);
+                Thread.Sleep(500);
+
+                if (i == 10)
+                {
+                    loopState.Break();
+                    return;
+                }
+            });
+            if (!loopResult.IsCompleted && loopResult.LowestBreakIteration.HasValue)
+            {
+                // You will never see this code becasue the loopResult IS ALWAYS COMPLETED :/
+                Console.WriteLine("Parallel Break (don't see me), loop encountered a break at {0}", loopResult.LowestBreakIteration.Value);
+            }
+            // loopResult IS ALWAYS COMPLETED!!! Break must be like a shutdown in Java Executor?
+            // The Tasks in the Queue will be executed but the Queue does not accept more tasks after shutdown.
+            // In this case are the Tasks the lambda expression?
+            // Depending on where you check the loopState in the lambda expression this could be a problem,
+            // you end up thinking the parallel for is completed when it is not. :/
+            Console.WriteLine("Parallel Break IsCompleted {0}", loopResult.IsCompleted);
+            Console.WriteLine("Parallel Break LowestBreakIteration {0}", loopResult.LowestBreakIteration.HasValue);
+            if (loopResult.LowestBreakIteration.HasValue)
+            {
+                Console.WriteLine("Parallel Break, loop encountered a break at {0}", loopResult.LowestBreakIteration.Value);
+            }
+
+
+            Console.WriteLine("Parallel Stop");
+            loopResult = Parallel.For(0, n, (i, loopState) =>
+            {
+                // What should I use? LowestBreakIteration, ShouldExitCurrentIteration or IsStopped? :(
+                if (loopState.LowestBreakIteration.HasValue || loopState.ShouldExitCurrentIteration || loopState.IsStopped)
+                {
+                    Console.WriteLine("Parallel Stop Check State {0}", i);
+                    loopState.Stop();
+                    return;
+                }
+
+                Console.WriteLine("Parallel Stop {0}", i);
+                Thread.Sleep(500);
+
+                if (i == 10)
+                {
+                    loopState.Stop();
+                    return;
+                }
+            });
+            // loopResult IS ALWAYS NOT COMPLETED!!! Stop must be like a shutdownNow in Java Executor?
+            // The Tasks in the Queue will NOT be executed and the Queue does not accept more tasks after shutdownNow.
+            // In this case are the Tasks the lambda expression?
+            if (!loopResult.IsCompleted && !loopResult.LowestBreakIteration.HasValue)
+            {
+                // When the Stop method is called, the index value of the iteration that caused the stop isn't available. :(
+                Console.WriteLine("Parallel Stop, loop was stopped");
+            }
+            Console.WriteLine("Parallel Stop IsCompleted {0}", loopResult.IsCompleted);
+            Console.WriteLine("Parallel Stop LowestBreakIteration {0}", loopResult.LowestBreakIteration.HasValue);
+
+
+            Console.WriteLine("CancellationToken");
+            var cts = new CancellationTokenSource();
+            cts.CancelAfter(600);
+            CancellationToken token = cts.Token;
+            var options = new ParallelOptions { CancellationToken = token };
+            try
+            {
+                Parallel.For(0, n, options, (i) =>
+                {
+                    // optionally check to see if cancellation happened
+                    if (token.IsCancellationRequested)
+                    {
+                        Console.WriteLine("CancellationToken Check State {0}", i);
+                        // optionally exit this iteration early
+                        return;
+                    }
+
+                    Console.WriteLine("CancellationToken {0}", i);
+                    Thread.Sleep(500);
+                });
+            }
+            catch (OperationCanceledException ex)
+            {
+                // I never see this message. Where is my exception? :(
+                Console.WriteLine("CancellationToken Exception {0}", ex.StackTrace);
+            }
+            // If external cancellation has been signaled and your loop has called either the
+            // Break or the Stop method of the ParallelLoopState object, a race occurs to see which
+            // will be recognized first. The parallel loop will either throw an OperationCanceledException
+            // or it will terminate using the mechanism for Break and Stop that is described in the section,
+            // "Breaking Out of Loops Early," earlier in this chapter.
+
+
+            // If the body of a parallel loop throws an unhandled exception, the parallel loop no longer begins
+            // any new steps. By default, iterations that are executing at the time of the exception,
+            // other than the iteration that threw the exception, will complete. After they finish, the parallel
+            // loop will throw an exception in the context of the thread that invoked it. Long-running iterations
+            // may want to test to see whether an exception is pending in another iteration. They can do this
+            // with the ParallelLoopState class's IsExceptional property. This property returns true if an exception is pending.
+
+            // Because more than one exception may occur during parallel execution, exceptions are grouped using
+            // an exception type known as an aggregate exception. The AggregateException class has an
+            // InnerExceptions property that contains a collection of all the exceptions that occurred during
+            // the execution of the parallel loop. Because the loop runs in parallel, there may be more than one exception.
+
+            // Exceptions take priority over external cancellations and terminations of a loop initiated by calling the
+            // Break or Stop methods of the ParallelLoopState object.
+
+
+            Console.WriteLine("Partitioner Default");
+            Parallel.ForEach(Partitioner.Create(0, n), range =>
+            {
+                Console.WriteLine("Partitioner Default range {0} {1}", range.Item1, range.Item2);
+                for (int i = range.Item1; i < range.Item2; i++)
+                {
+                    // very small, equally sized blocks of work
+                    Console.WriteLine("Partitioner Default {0}", i);
+                }
+            });
+            // The number of ranges that will be created by a Partitioner object depends on the number of cores in your computer.
+            // The default number of ranges is approximately three times the number of those cores.
+
+
+            // If you know how big you want your ranges to be, you can use an overloaded version of the Partitioner.Create method
+            // that allows you to specify the size of each range. Here's an example.
+            Console.WriteLine("Partitioner Custom");
+            Parallel.ForEach(Partitioner.Create(0, n, n/2), range =>
+            {
+                Console.WriteLine("Partitioner Custom range {0} {1}", range.Item1, range.Item2);
+                for (int i = range.Item1; i < range.Item2; i++)
+                {
+                    // small, equally sized blocks of work
+                    Console.WriteLine("Partitioner Custom {0}", i);
+                }
+            });
+
+
+            // You can limit the maximum number of tasks used concurrently by specifying the MaxDegreeOfParallelism property of
+            // a ParallelOptions object. Here is an example
+            Console.WriteLine("Parallel.For MaxDegreeOfParallelism 2");
+            options = new ParallelOptions() { MaxDegreeOfParallelism = 2 };
+            Parallel.For(0, n, options, i =>
+            {
+                Console.WriteLine("Parallel.For MaxDegreeOfParallelism {0}", i);
+                Thread.Sleep(500);
+            });
+
+
+            Console.WriteLine("PLINQ, ForAll WithDegreeOfParallelism 2");
+            listOfStrings.AsParallel().WithDegreeOfParallelism(2).ForAll(stringnumber =>
+            {
+                Console.WriteLine("PLINQ, ForAll WithDegreeOfParallelism {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+
+
+            // Here's an example that uses one of the overloads of the Parallel.ForEach method. The example uses a Partitioner
+            // object to decompose the work into relatively large pieces, because the amount of work performed by each step is small,
+            // and there are a large number of steps.
+            Console.WriteLine("Task-Local State in a Loop Body");
+            int numberOfSteps = 10000000;
+            double[] results = new double[numberOfSteps];
+            Parallel.ForEach(Partitioner.Create(0, numberOfSteps/2),
+                             new ParallelOptions(),
+                             () => { return new Random(); },
+                             (range, loopState, random) =>
+                             {
+                                for (int i = range.Item1; i < range.Item2; i++)
+                                results[i] = random.NextDouble();
+                                return random;
+                             },
+                             _ => {}
+            );
+
+
+            // Console.WriteLine("Custom Task Scheduler for a Parallel Loop");
+            // TaskScheduler myScheduler = How???? :(
+            // options = new ParallelOptions() { TaskScheduler = myScheduler };
+            // Parallel.For(0, n, options, i =>
+            // {
+            //    Console.WriteLine("Custom Task Scheduler for a Parallel Loop {0}", i);
+            //    Thread.Sleep(500);
+            // });
+
+
+            // Use ForAll if you need to iterate over a PLINQ result. Don't use ForEach in this case.
+            Console.WriteLine("PLINQ's ForAll the same as Parallel.ForEach");
+            var plinqforall = from stringnumber in listOfStrings.AsParallel() select new String(stringnumber.ToCharArray());
+            // This is what I should have used instead :)
+            plinqforall.ForAll(stringnumber =>
+            {
+                Console.WriteLine("PLINQ's ForAll the same as Parallel.ForEach {0}", stringnumber);
+                Thread.Sleep(500);
+            });
+
+
+
+            // Be careful if you use parallel loops with individual steps that take several seconds or more.
+            // This can occur with I/O-bound workloads as well as lengthy calculations. If the loops take a long
+            // time, you may experience an unbounded growth of worker threads due to a heuristic for preventing thread starvation
+            // that's used by the .NET ThreadPool class's thread injection logic. This heuristic steadily increases the number
+            // of worker threads when work items of the current pool run for long periods of time. The motivation is to add
+            // more threads in cases where everything in the thread pool is blocked. Unfortunately, if work is actually proceeding,
+            // more threads may not necessarily be what you want. The .NET Framework can't distinguish between these two situations.
+
+            //If the individual steps of your loop take a long time, you may see more worker threads than you intend.
+        }
+    }
+}
+
diff --git a/Allgemeines/Threads/Threads/Program.cs b/Allgemeines/Threads/Threads/Program.cs
new file mode 100644 (file)
index 0000000..641fd36
--- /dev/null
@@ -0,0 +1,17 @@
+using System;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Collections.Generic;
+using System.Linq;
+using System.Collections.Concurrent;
+
+namespace Threads
+{
+    class MainClass
+    {
+        public static void Main(string[] args)
+        {
+            Chapter2.Test();
+        }
+    }
+}
diff --git a/Allgemeines/Threads/Threads/Properties/AssemblyInfo.cs b/Allgemeines/Threads/Threads/Properties/AssemblyInfo.cs
new file mode 100644 (file)
index 0000000..bd10a4c
--- /dev/null
@@ -0,0 +1,22 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+[assembly: AssemblyTitle("Threads")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("gumartinm.name")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("gumartinm.name")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+[assembly: AssemblyVersion("1.0.*")]
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
diff --git a/Allgemeines/Threads/Threads/Threads.csproj b/Allgemeines/Threads/Threads/Threads.csproj
new file mode 100644 (file)
index 0000000..4a0a00c
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
+    <ProductVersion>12.0.0</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{CED02136-FA8A-4E92-9BC6-3340DA89C3A4}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Threads</RootNamespace>
+    <AssemblyName>Threads</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <Description>Learning about threads and C#
+See: http://parallelpatterns.codeplex.com/</Description>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+    <PlatformTarget>x86</PlatformTarget>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Chapter2.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project>
\ No newline at end of file