diff --git a/Networking.sln b/Networking.sln index 3105fee..56fe706 100644 --- a/Networking.sln +++ b/Networking.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.31911.196 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Networking", "Networking\Networking.csproj", "{85D8795C-E9DC-4A59-B669-E2A8EEAC7A9E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp1", "WpfApp1\WpfApp1.csproj", "{89252909-F8E2-4BDB-8EA9-4DA1A329545C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {85D8795C-E9DC-4A59-B669-E2A8EEAC7A9E}.Debug|Any CPU.Build.0 = Debug|Any CPU {85D8795C-E9DC-4A59-B669-E2A8EEAC7A9E}.Release|Any CPU.ActiveCfg = Release|Any CPU {85D8795C-E9DC-4A59-B669-E2A8EEAC7A9E}.Release|Any CPU.Build.0 = Release|Any CPU + {89252909-F8E2-4BDB-8EA9-4DA1A329545C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89252909-F8E2-4BDB-8EA9-4DA1A329545C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89252909-F8E2-4BDB-8EA9-4DA1A329545C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89252909-F8E2-4BDB-8EA9-4DA1A329545C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Networking/IPScannerLib/HiResTimer.cs b/Networking/IPScannerLib/HiResTimer.cs new file mode 100644 index 0000000..3dcb3a3 --- /dev/null +++ b/Networking/IPScannerLib/HiResTimer.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; + +namespace IPScanner +{ + public class HiResTimer + { + private bool isPerfCounterSupported = false; + private Int64 frequency = 0; + + // Windows CE native library with QueryPerformanceCounter(). + private const string lib = "Kernel32.dll"; + [DllImport(lib)] + private static extern int QueryPerformanceCounter(ref Int64 count); + [DllImport(lib)] + private static extern int QueryPerformanceFrequency(ref Int64 frequency); + + public HiResTimer() + { + // Query the high-resolution timer only if it is supported. + // A returned frequency of 1000 typically indicates that it is not + // supported and is emulated by the OS using the same value that is + // returned by Environment.TickCount. + // A return value of 0 indicates that the performance counter is + // not supported. + int returnVal = QueryPerformanceFrequency(ref frequency); + + if (returnVal != 0 && frequency != 1000) + { + // The performance counter is supported. + isPerfCounterSupported = true; + } + else + { + // The performance counter is not supported. Use + // Environment.TickCount instead. + frequency = 1000; + } + } + + private Int64 Frequency + { + get + { + return frequency; + } + } + + private Int64 Value + { + get + { + if (isPerfCounterSupported) + { + // Get the value here if the counter is supported. + Int64 tickCount = 0; + QueryPerformanceCounter(ref tickCount); + return tickCount; + } + else + { + // Otherwise, use Environment.TickCount. + return (Int64)Environment.TickCount; + } + } + } + + private Int64 start; + private bool isRunning = false; + private double elapsedMillisecondsAtTimeOfStop = 0; + public double ElapsedMilliseconds + { + get + { + if (isRunning) + { + Int64 timeElapsedInTicks = Value - start; + return (timeElapsedInTicks * 1000) / Frequency; + } + else + return elapsedMillisecondsAtTimeOfStop; + } + } + public void Start() + { + start = Value; + isRunning = true; + } + public void Stop() + { + if (!isRunning) + return; + elapsedMillisecondsAtTimeOfStop = ElapsedMilliseconds; + isRunning = false; + } + public void Reset() + { + isRunning = false; + elapsedMillisecondsAtTimeOfStop = 0; + } + } +} diff --git a/Networking/IPScannerLib/HttpHelper.cs b/Networking/IPScannerLib/HttpHelper.cs new file mode 100644 index 0000000..d786904 --- /dev/null +++ b/Networking/IPScannerLib/HttpHelper.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.IO; +using System.Threading; +using System.Reflection; +using System.Net.Configuration; + +namespace IPScanner +{ + public class HttpResponseData + { + public string data; + public SortedList headers; + public string host; + public HttpResponseData(string data, SortedList headers, string host) + { + this.data = data; + this.headers = headers; + this.host = host; + } + public string GetHeaderValue(string key) + { + string val; + if (headers.TryGetValue(key.ToLower(), out val)) + return val; + return ""; + } + } + internal static class HttpHelper + { + static HttpHelper() + { + ToggleAllowUnsafeHeaderParsing(true); + } + public static HttpResponseData GetHttpResponseData(string url) + { + SortedList headers = new SortedList(); + //return new HttpResponseData("", headers, url); + byte[] data = GetData(url, headers); + return new HttpResponseData(UTF8Encoding.UTF8.GetString(data), headers, url); + } + /// + /// Gets data from a URL and returns it as a byte array. + /// + /// + /// + public static byte[] GetData(string url, SortedList headers = null, string user = "", string password = "", bool keepAlive = false) + { + try + { + if (url.Contains(".80")) + { + Console.WriteLine(url); + } + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url); + webRequest.Proxy = null; + webRequest.KeepAlive = keepAlive; + webRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + + if (!string.IsNullOrEmpty(user) || !string.IsNullOrEmpty(password)) + { + string authInfo = user + ":" + password; + authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo)); + webRequest.Headers["Authorization"] = "Basic " + authInfo; + } + webRequest.Method = "GET"; + webRequest.Timeout = 5000; + webRequest.AllowAutoRedirect = true; + return GetResponse(webRequest, headers); + } + catch (ThreadAbortException ex) { throw ex; } + catch (WebException ex) + { + if (ex.Message.StartsWith("The server committed a protocol violation")) + return UTF8Encoding.UTF8.GetBytes(ex.Message); + if (ex.Message == "The remote server returned an error: (404) Not Found." || ex.Message == "The remote server returned an error: (401) Unauthorized.") + { + + //if(ex.Response.ResponseUri.AbsolutePath == "/nocookies.html") + try + { + return GetResponseData(ex.Response, headers); + } + catch (ThreadAbortException e) { throw e; } + catch (Exception e) + { + if (url.Contains(".80")) + { + Console.WriteLine(e.ToString()); + } + } + } + //else if (ex.Message == "The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel." && url.StartsWith("http:")) + //{ + // url = "https" + url.Substring(4); + // return GetData(url, headers, user, password, keepAlive); + //} + } + catch (Exception ex) + { + if (url.Contains(".80")) + { + Console.WriteLine(ex.ToString()); + } + } + return new byte[0]; + } + private static byte[] GetResponse(HttpWebRequest webRequest, SortedList headers = null) + { + return GetResponseData((HttpWebResponse)webRequest.GetResponse(), headers); + } + + private static byte[] GetResponseData(WebResponse webResponseObj, SortedList headers = null) + { + byte[] data; + using (HttpWebResponse webResponse = (HttpWebResponse)webResponseObj) + { + using (MemoryStream ms = new MemoryStream()) + { + using (Stream responseStream = webResponse.GetResponseStream()) + { + // Dump the response stream into the MemoryStream ms + int bytesRead = 1; + while (bytesRead > 0) + { + byte[] buffer = new byte[8000]; + bytesRead = responseStream.Read(buffer, 0, buffer.Length); + if (bytesRead > 0) + ms.Write(buffer, 0, bytesRead); + } + data = new byte[ms.Length]; + + // Dump the data into the byte array + ms.Seek(0, SeekOrigin.Begin); + ms.Read(data, 0, data.Length); + responseStream.Close(); + + if (headers != null) + foreach (string key in webResponse.Headers.AllKeys) + headers[key.ToLower()] = webResponse.Headers[key]; + } + } + webResponse.Close(); + } + return data; + } + + /// + /// Enable/disable useUnsafeHeaderParsing. + /// See http://o2platform.wordpress.com/2010/10/20/dealing-with-the-server-committed-a-protocol-violation-sectionresponsestatusline/ + /// + /// + /// + public static bool ToggleAllowUnsafeHeaderParsing(bool enable) + { + //Get the assembly that contains the internal class + Assembly assembly = Assembly.GetAssembly(typeof(SettingsSection)); + if (assembly != null) + { + //Use the assembly in order to get the internal type for the internal class + Type settingsSectionType = assembly.GetType("System.Net.Configuration.SettingsSectionInternal"); + if (settingsSectionType != null) + { + //Use the internal static property to get an instance of the internal settings class. + //If the static instance isn't created already invoking the property will create it for us. + object anInstance = settingsSectionType.InvokeMember("Section", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.NonPublic, null, null, new object[] { }); + if (anInstance != null) + { + //Locate the private bool field that tells the framework if unsafe header parsing is allowed + FieldInfo aUseUnsafeHeaderParsing = settingsSectionType.GetField("useUnsafeHeaderParsing", BindingFlags.NonPublic | BindingFlags.Instance); + if (aUseUnsafeHeaderParsing != null) + { + aUseUnsafeHeaderParsing.SetValue(anInstance, enable); + return true; + } + + } + } + } + return false; + } + } +} diff --git a/Networking/IPScannerLib/IPRanges.cs b/Networking/IPScannerLib/IPRanges.cs new file mode 100644 index 0000000..29d0f61 --- /dev/null +++ b/Networking/IPScannerLib/IPRanges.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Text; + +namespace IPScanner +{ + public static class IPRanges + { + public static List> GetOperationalIPRanges() + { + List> ranges = new List>(); + + foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces()) + { + if (netInterface.OperationalStatus != OperationalStatus.Up) + continue; + IPInterfaceProperties ipProps = netInterface.GetIPProperties(); + foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses) + { + if (addr.Address.AddressFamily == AddressFamily.InterNetwork) + ranges.Add(new Tuple(GetLowestInRange(addr.Address, addr.IPv4Mask), GetHighestInRange(addr.Address, addr.IPv4Mask))); + } + } + return ranges; + } + private static IPAddress GetLowestInRange(IPAddress address, IPAddress mask) + { + byte[] addressBytes = address.GetAddressBytes(); + byte[] maskBytes = mask.GetAddressBytes(); + if (addressBytes.Length != 4 || maskBytes.Length != 4) + return IPAddress.None; + byte[] lowest = new byte[4]; + for (var i = 0; i < 4; i++) + lowest[i] = (byte)(addressBytes[i] & maskBytes[i]); + return new IPAddress(lowest); + } + private static IPAddress GetHighestInRange(IPAddress address, IPAddress mask) + { + byte[] addressBytes = address.GetAddressBytes(); + byte[] maskBytes = mask.GetAddressBytes(); + if (addressBytes.Length != 4 || maskBytes.Length != 4) + return IPAddress.None; + byte[] highest = new byte[4]; + for (var i = 0; i < 4; i++) + highest[i] = (byte)((addressBytes[i] & maskBytes[i]) | ~maskBytes[i]); + return new IPAddress(highest); + } + } +} diff --git a/Networking/IPScannerLib/IPScanResult.cs b/Networking/IPScannerLib/IPScanResult.cs new file mode 100644 index 0000000..61c3e4b --- /dev/null +++ b/Networking/IPScannerLib/IPScanResult.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; + +namespace IPScanner +{ + public class IPScanResult + { + public IPAddress ip; + public int ping = -1; + public string host; + public ScanStatus status = ScanStatus.Initializing; + public string identification = "..."; + public HttpResponseData response; + + public IPScanResult(IPAddress ip) + { + this.ip = ip; + } + //public IPScanResult(IPAddress ip, int ping, string host) + //{ + // this.ip = ip; + // this.ping = ping; + // this.host = host; + // this.status = ScanStatus.Complete; + //} + } +} diff --git a/Networking/IPTool.cs b/Networking/IPScannerLib/IPTool.cs similarity index 100% rename from Networking/IPTool.cs rename to Networking/IPScannerLib/IPTool.cs diff --git a/Networking/IPScannerLib/NetworkScanner.cs b/Networking/IPScannerLib/NetworkScanner.cs new file mode 100644 index 0000000..732db69 --- /dev/null +++ b/Networking/IPScannerLib/NetworkScanner.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Sockets; +using Amib.Threading; +using System.Net.NetworkInformation; +using System.Text.RegularExpressions; +using System.Diagnostics; + +namespace IPScanner +{ + public class NetworkScanner + { + private static Regex rxHtmlTitle = new Regex("([^<]+?)", RegexOptions.Compiled); + SmartThreadPool Pool = new SmartThreadPool(1000, 256, 0); + public NetworkScanner() + { + ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; + System.Net.ServicePointManager.MaxServicePoints = int.MaxValue; + } + + public List BeginScan(IPAddress ipFrom, IPAddress ipTo) + { + Amib.Threading.Action, int> ipScanAction = new Amib.Threading.Action, int>(ScanIPAsync); + // Count the IP addresses included in this range + byte[] addyEnd = ipTo.GetAddressBytes(); + byte[] addyNext = ipFrom.GetAddressBytes(); + + List Results = new List(); + while (CompareIPs(addyNext, addyEnd) < 1) + { + Results.Add(new IPScanResult(new IPAddress(addyNext))); + IncrementIP(addyNext); + } + + for (int i = 0; i < Results.Count; i++) + Pool.QueueWorkItem(ipScanAction, Results[i].ip, Results, i); + return Results; + } + private void ScanIPAsync(IPAddress ip, List results, int listIndex) + { + bool foundHost = false; + results[listIndex].status = ScanStatus.Initializing; + + // Attempt Ordinary Ping + try + { + using (Ping p = new Ping()) + { + PingReply pingReply = p.Send(ip, 5000); + if (pingReply.Status == IPStatus.Success) + { + foundHost = true; + results[listIndex].status = ScanStatus.Partial; + results[listIndex].ping = (int)pingReply.RoundtripTime; + } + } + } + catch (SocketException) + { + } + catch (Exception) + { + } + + // Attempt DNS Lookup + try + { + Stopwatch timer = new Stopwatch(); + timer.Start(); + IPHostEntry ipe = Dns.GetHostEntry(ip); + timer.Stop(); + int dnsLookupTime = (int)timer.ElapsedMilliseconds; + + foundHost = true; + //if (results[listIndex].ping < 0 || dnsLookupTime < results[listIndex].ping) + // results[listIndex].ping = dnsLookupTime; + results[listIndex].host = ipe.HostName.ToString(); + results[listIndex].status = ScanStatus.Complete; + } + //catch (SocketException ex) + //{ + // //if (ex.SocketErrorCode == SocketError.HostNotFound) + // // return; + // Console.WriteLine(ex.Message); + //} + catch (Exception) + { + } + + + + if (foundHost) + { + // Try to identify + HttpResponseData response; + results[listIndex].identification = IdentifyHost(ip, out response); + results[listIndex].status = ScanStatus.Complete; + results[listIndex].response = response; + } + else + results[listIndex].status = ScanStatus.NotFound; + } + + private string IdentifyHost(IPAddress ip, out HttpResponseData response) + { + response = null; + Stopwatch sw = new Stopwatch(); + try + { + sw.Start(); + response = HttpHelper.GetHttpResponseData("http://" + ip.ToString() + "/"); + if (response.GetHeaderValue("server").StartsWith("lighttpd") && response.GetHeaderValue("set-cookie").Contains("AIROS_") && response.data.Contains("Error 404")) + return "Ubiquiti"; + else if (response.GetHeaderValue("server").StartsWith("Boa") && response.data.Contains("<OBJECT ID=\"TSConfigIPCCtrl\"")) + return "Generic IP Cam"; // CCDCam EC-IP5911 + else if (response.data.Contains("flow_slct = get_slctid('flowtype');")) + return "IPS Cam"; + else if (response.GetHeaderValue("server") == "GoAhead-Webs" && response.data.Contains("document.location = '/live.asp?")) + return "Edimax Cam"; + else if (response.GetHeaderValue("server").StartsWith("App-webs/") && response.data.Contains("window.location.href = \"doc/page/login.asp")) + return "Hikvision"; + else if (response.data.Contains("src=\"jsCore/LAB.js\"") || response.data.Contains("var lt = \"?WebVersion=") || response.data.Contains("src=\"jsCore/rpcCore.js")) + return "Dahua"; + else if (response.GetHeaderValue("www-authenticate").Contains("realm=\"tomato\"")) + return "Tomato"; + else if (response.GetHeaderValue("server") == "Web Server" && response.data.Contains("<TITLE>NETGEAR FS728TP")) + return "Netgear FS728TP"; + else if (response.GetHeaderValue("set-cookie").Contains("DLILPC=") && response.data.Contains("Power Controller")) + return "Web Power Switch"; + else if (response.data == "The server committed a protocol violation. Section=ResponseStatusLine") + return "? WeatherDirect ?"; + else if (response.data == "The server committed a protocol violation. Section=ResponseHeader Detail=CR must be followed by LF") + return "? Web Power Switch ?"; + else if (response.data.Contains("NetDAQ ND-100")) + return "NetDAQ ND-100"; + else if (response.GetHeaderValue("server") == "nginx" && response.data.Contains("<title>airVision:")) + return "AirVision NVR"; + else if (response.GetHeaderValue("server") == "nginx" && response.data.Contains("<title>airVision:")) + return "AirVision NVR"; + else if (response.GetHeaderValue("server").StartsWith("BlueIris-")) + return "Blue Iris"; + //else if (response.data.Contains("<title>iTach")) + // return "iTach"; + else if (response.data.Contains("href=\"/cmh\"")) + return "Vera"; + else if (response.data.Contains("WDMyCloud")) + return "WDMyCloud"; + //else if (response.data.Contains("<title>DD-WRT")) + // return "DD-WRT"; + else if (response.data.Contains("= \"Peplink\"")) + return "Peplink"; + else if (response.data.Contains("GSViewerX.ocx")) + return "GrandStream"; + else if (response.data.Contains("content=\"Canon Inc.\"")) + return "Canon printer"; + else if (response.GetHeaderValue("server") == "tsbox" && response.GetHeaderValue("www-authenticate") == "Basic realm=\"pbox\"") + return "HDMI Encoder"; + else if (response.data.Contains("Rules of login password.\\n")) + return "ACTi"; + else if (response.data.Contains("/static/freenas_favicon.ico")) + return "FreeNAS"; + else if (response.data.Contains("CONTENT=\"0;url=cgi-bin/kvm.cgi\"")) + return "Avocent KVM"; + else if (response.GetHeaderValue("www-authenticate") == "Basic realm=\"TomatoUSB\"") + return "TomatoUSB Router"; + else if (response.GetHeaderValue("auther") == "Steven Wu" && response.GetHeaderValue("server") == "Camera Web Server/1.0" && response.data.Contains("location.href=\"top.htm?Currenttime=\"+timeValue;")) + return "TrendNET IP cam"; + else if (response.data.Contains(@"<meta http-equiv=""refresh"" content=""0;URL='/ui'""/>")) + return "ESXi"; + else if (response.GetHeaderValue("server") == "Microsoft-HTTPAPI/2.0") + return "IIS"; + else + { + Match m = rxHtmlTitle.Match(response.data); + if (m.Success) + return m.Groups[1].Value; + string server = response.GetHeaderValue("server"); + if (!string.IsNullOrEmpty(server)) + return server; + return ""; + } + return response.data; + } + catch (Exception) + { + } + finally + { + sw.Stop(); + //Console.WriteLine("Spent " + sw.ElapsedMilliseconds + " on " + response.data.Length); + } + return ""; + } + + public void Abort() + { + Pool.Cancel(true); + } + bool ArraysMatch(Array a1, Array a2) + { + if (a1.Length != a2.Length) + return false; + for (int i = 0; i < a1.Length; i++) + if (a1.GetValue(i) != a1.GetValue(i)) + return false; + return true; + } + int CompareIPs(byte[] ip1, byte[] ip2) + { + if (ip1 == null || ip1.Length != 4) + return -1; + if (ip2 == null || ip2.Length != 4) + return 1; + int comp = ip1[0].CompareTo(ip2[0]); + if (comp == 0) + comp = ip1[1].CompareTo(ip2[1]); + if (comp == 0) + comp = ip1[2].CompareTo(ip2[2]); + if (comp == 0) + comp = ip1[3].CompareTo(ip2[3]); + return comp; + } + void IncrementIP(byte[] ip, int idx = 3) + { + if (ip == null || ip.Length != 4 || idx < 0) + return; + if (ip[idx] == 254) + { + ip[idx] = 1; + IncrementIP(ip, idx - 1); + } + else + ip[idx] = (byte)(ip[idx] + 1); + } + } +} diff --git a/Networking/IPScannerLib/ScanStatus.cs b/Networking/IPScannerLib/ScanStatus.cs new file mode 100644 index 0000000..ce9cf2b --- /dev/null +++ b/Networking/IPScannerLib/ScanStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IPScanner +{ + public enum ScanStatus + { + Initializing, + Scanning, + NotFound, + Complete, + Partial + } +} diff --git a/Networking/Networking.csproj b/Networking/Networking.csproj index 054aefd..8739f68 100644 --- a/Networking/Networking.csproj +++ b/Networking/Networking.csproj @@ -37,10 +37,13 @@ <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> <Reference Include="PTConverter.Plugin, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\PTConverter.Plugin.1.0.2\lib\net472\PTConverter.Plugin.dll</HintPath> + <HintPath>..\packages\PTConverter.Plugin.1.0.3\lib\net472\PTConverter.Plugin.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Web" /> + <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xaml" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> @@ -48,17 +51,71 @@ <Reference Include="System.Data" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Xml" /> + <Reference Include="UIAutomationProvider" /> <Reference Include="WindowsBase" /> + <Reference Include="WindowsFormsIntegration" /> </ItemGroup> <ItemGroup> - <Compile Include="IPTool.cs" /> - <Compile Include="Networking.IPv4.xaml.cs"> - <DependentUpon>Networking.IPv4.xaml</DependentUpon> + <Compile Include="IPScannerLib\HiResTimer.cs" /> + <Compile Include="IPScannerLib\HttpHelper.cs" /> + <Compile Include="IPScannerLib\IPRanges.cs" /> + <Compile Include="IPScannerLib\IPScanResult.cs" /> + <Compile Include="IPScannerLib\IPTool.cs" /> + <Compile Include="IPScannerLib\NetworkScanner.cs" /> + <Compile Include="IPScannerLib\ScanStatus.cs" /> + <Compile Include="Pages\IPScanner.xaml.cs"> + <DependentUpon>IPScanner.xaml</DependentUpon> </Compile> + <Compile Include="Pages\IPv4.xaml.cs"> + <DependentUpon>IPv4.xaml</DependentUpon> + </Compile> + <Compile Include="Pages\PortScanner.xaml.cs"> + <DependentUpon>PortScanner.xaml</DependentUpon> + </Compile> + <Compile Include="Pages\WF_IPScanner.cs"> + <SubType>UserControl</SubType> + </Compile> + <Compile Include="Pages\WF_IPScanner.Designer.cs"> + <DependentUpon>WF_IPScanner.cs</DependentUpon> + </Compile> + <Compile Include="PluginInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SmartThreadPool\CallerThreadContext.cs" /> + <Compile Include="SmartThreadPool\CanceledWorkItemsGroup.cs" /> + <Compile Include="SmartThreadPool\EventWaitHandle.cs" /> + <Compile Include="SmartThreadPool\EventWaitHandleFactory.cs" /> + <Compile Include="SmartThreadPool\Exceptions.cs" /> + <Compile Include="SmartThreadPool\Interfaces.cs" /> + <Compile Include="SmartThreadPool\InternalInterfaces.cs" /> + <Compile Include="SmartThreadPool\PriorityQueue.cs" /> + <Compile Include="SmartThreadPool\SLExt.cs" /> + <Compile Include="SmartThreadPool\SmartThreadPool.cs" /> + <Compile Include="SmartThreadPool\SmartThreadPool.ThreadEntry.cs" /> + <Compile Include="SmartThreadPool\Stopwatch.cs" /> + <Compile Include="SmartThreadPool\STPEventWaitHandle.cs" /> + <Compile Include="SmartThreadPool\STPPerformanceCounter.cs" /> + <Compile Include="SmartThreadPool\STPStartInfo.cs" /> + <Compile Include="SmartThreadPool\SynchronizedDictionary.cs" /> + <Compile Include="SmartThreadPool\WIGStartInfo.cs" /> + <Compile Include="SmartThreadPool\WorkItem.cs" /> + <Compile Include="SmartThreadPool\WorkItem.WorkItemResult.cs" /> + <Compile Include="SmartThreadPool\WorkItemFactory.cs" /> + <Compile Include="SmartThreadPool\WorkItemInfo.cs" /> + <Compile Include="SmartThreadPool\WorkItemResultTWrapper.cs" /> + <Compile Include="SmartThreadPool\WorkItemsGroup.cs" /> + <Compile Include="SmartThreadPool\WorkItemsGroupBase.cs" /> + <Compile Include="SmartThreadPool\WorkItemsQueue.cs" /> </ItemGroup> <ItemGroup> - <Page Include="Networking.IPv4.xaml"> + <Page Include="Pages\IPScanner.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Pages\IPv4.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> + <Page Include="Pages\PortScanner.xaml"> <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> @@ -69,5 +126,10 @@ <ItemGroup> <None Include="packages.config" /> </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="Pages\WF_IPScanner.resx"> + <DependentUpon>WF_IPScanner.cs</DependentUpon> + </EmbeddedResource> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project> \ No newline at end of file diff --git a/Networking/Pages/IPScanner.xaml b/Networking/Pages/IPScanner.xaml new file mode 100644 index 0000000..94aab39 --- /dev/null +++ b/Networking/Pages/IPScanner.xaml @@ -0,0 +1,12 @@ +<UserControl x:Class="Networking.Pages.IPScanner" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:Networking.Pages" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800" Background="White"> + <WindowsFormsHost HorizontalAlignment="Left" Height="450" VerticalAlignment="Top" Width="800"> + <local:WF_IPScanner/> + </WindowsFormsHost> +</UserControl> diff --git a/Networking/Pages/IPScanner.xaml.cs b/Networking/Pages/IPScanner.xaml.cs new file mode 100644 index 0000000..98003a8 --- /dev/null +++ b/Networking/Pages/IPScanner.xaml.cs @@ -0,0 +1,63 @@ +using IPScanner; +using PTConverter.Plugin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Timers; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Networking.Pages +{ + /// <summary> + /// Interaktionslogik für Networking.xaml + /// </summary> + public partial class IPScanner : UserControl, IPage + { + + public IPScanner() + { + InitializeComponent(); + } + + public string GetCategory() => ""; + + public UserControl GetPage() => new IPScanner(); + + public string GetUnderCategory() => ""; + + string IPtoString(byte[] array) + { + // + // Concatenate all the elements into a StringBuilder. + // + StringBuilder strinbuilder = new StringBuilder(); + for (int i = 0; i < array.Count(); i++) + { + + strinbuilder.Append(array[i]); + if (i != array.Count() - 1) + strinbuilder.Append('.'); + } + return strinbuilder.ToString(); + } + + private string GetPingTime(IPScanResult result) + { + if (result.ping > -1) + return result.ping + " ms"; + return "N/A"; + } + + } +} diff --git a/Networking/Networking.IPv4.xaml b/Networking/Pages/IPv4.xaml similarity index 98% rename from Networking/Networking.IPv4.xaml rename to Networking/Pages/IPv4.xaml index cb8a2c4..ef8965e 100644 --- a/Networking/Networking.IPv4.xaml +++ b/Networking/Pages/IPv4.xaml @@ -1,9 +1,9 @@ -<UserControl x:Class="Networking.Networking_IPv4" +<UserControl x:Class="Networking.Pages.IPv4" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:Networking" + xmlns:local="clr-namespace:Networking.Pages" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" Background="White"> <Grid> diff --git a/Networking/Networking.IPv4.xaml.cs b/Networking/Pages/IPv4.xaml.cs similarity index 94% rename from Networking/Networking.IPv4.xaml.cs rename to Networking/Pages/IPv4.xaml.cs index 2aaa2fa..846c007 100644 --- a/Networking/Networking.IPv4.xaml.cs +++ b/Networking/Pages/IPv4.xaml.cs @@ -15,37 +15,23 @@ using System.Windows.Shapes; using NetCalc; using PTConverter.Plugin; -namespace Networking +namespace Networking.Pages { /// <summary> /// Interaktionslogik für Networking.xaml /// </summary> - public partial class Networking_IPv4 : UserControl, IPlugin, IPage + public partial class IPv4 : UserControl, IPage { #region Declarations protected IPTool ip = null; protected bool fill = false; // lock events wenn updating protected bool isIP = false; - - public string Author => "Kevin Krüger"; - - public string Company => "PeaceToke"; - - public string PluginName => "Networking"; - - public string Description => "Calculate networks very easy!"; - - public string Version => "1.0.0"; - - public string IconLink => "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.clipartmax.com%2Fpng%2Fmiddle%2F276-2769230_networking-deep-neural-network-icon.png&f=1&nofb=1"; - - public IPage Page => this; #endregion private string _ipAddress = "0.0.0.0"; private string IPAddress { get => tbIPAddressP1.Text + "." + tbIPAddressP2.Text + "." + tbIPAddressP3.Text + "." + tbIPAddressP4.Text; set => _ipAddress = value; } - public Networking_IPv4() + public IPv4() { InitializeComponent(); @@ -61,7 +47,7 @@ namespace Networking } public string GetCategory() => "Networking"; public string GetUnderCategory() => "IPv4"; - public UserControl GetPage() => new Networking_IPv4(); + public UserControl GetPage() => new IPv4(); #region Methods Used protected void newIP() diff --git a/Networking/Pages/PortScanner.xaml b/Networking/Pages/PortScanner.xaml new file mode 100644 index 0000000..c466d71 --- /dev/null +++ b/Networking/Pages/PortScanner.xaml @@ -0,0 +1,12 @@ +<UserControl x:Class="Networking.Pages.PortScanner" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:Networking.Pages" + mc:Ignorable="d" + d:DesignHeight="450" d:DesignWidth="800" Background="White"> + <Grid> + + </Grid> +</UserControl> diff --git a/Networking/Pages/PortScanner.xaml.cs b/Networking/Pages/PortScanner.xaml.cs new file mode 100644 index 0000000..5672db3 --- /dev/null +++ b/Networking/Pages/PortScanner.xaml.cs @@ -0,0 +1,35 @@ +using PTConverter.Plugin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Networking.Pages +{ + /// <summary> + /// Interaktionslogik für Networking.xaml + /// </summary> + public partial class PortScanner : UserControl, IPage + { + public PortScanner() + { + InitializeComponent(); + } + + public string GetCategory() => "Networking"; + + public UserControl GetPage() => new PortScanner(); + + public string GetUnderCategory() => "PortScan"; + } +} diff --git a/Networking/Pages/WF_IPScanner.Designer.cs b/Networking/Pages/WF_IPScanner.Designer.cs new file mode 100644 index 0000000..be4f568 --- /dev/null +++ b/Networking/Pages/WF_IPScanner.Designer.cs @@ -0,0 +1,133 @@ + +namespace Networking.Pages +{ + partial class WF_IPScanner + { + /// <summary> + /// Erforderliche Designervariable. + /// </summary> + private System.ComponentModel.IContainer components = null; + + /// <summary> + /// Verwendete Ressourcen bereinigen. + /// </summary> + /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param> + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Komponenten-Designer generierter Code + + /// <summary> + /// Erforderliche Methode für die Designerunterstützung. + /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. + /// </summary> + private void InitializeComponent() + { + this.lvIPList = new System.Windows.Forms.ListView(); + this.chIP = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chPing = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chHost = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.chRecognized = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.btnScan = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.tbIPRange = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // lvIPList + // + this.lvIPList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lvIPList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.chIP, + this.chPing, + this.chHost, + this.chRecognized}); + this.lvIPList.FullRowSelect = true; + this.lvIPList.HideSelection = false; + this.lvIPList.Location = new System.Drawing.Point(12, 87); + this.lvIPList.MultiSelect = false; + this.lvIPList.Name = "lvIPList"; + this.lvIPList.Size = new System.Drawing.Size(714, 282); + this.lvIPList.TabIndex = 1; + this.lvIPList.UseCompatibleStateImageBehavior = false; + this.lvIPList.View = System.Windows.Forms.View.Details; + // + // chIP + // + this.chIP.Text = "IP"; + this.chIP.Width = 93; + // + // chPing + // + this.chPing.Text = "Ping"; + // + // chHost + // + this.chHost.Text = "Host"; + this.chHost.Width = 88; + // + // chRecognized + // + this.chRecognized.Text = "Recognized as"; + this.chRecognized.Width = 119; + // + // btnScan + // + this.btnScan.Location = new System.Drawing.Point(323, 58); + this.btnScan.Name = "btnScan"; + this.btnScan.Size = new System.Drawing.Size(75, 23); + this.btnScan.TabIndex = 7; + this.btnScan.Text = "Scan"; + this.btnScan.UseVisualStyleBackColor = true; + this.btnScan.Click += new System.EventHandler(this.btnScan_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(9, 64); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(50, 13); + this.label1.TabIndex = 6; + this.label1.Text = "IP range:"; + // + // tbIPRange + // + this.tbIPRange.Location = new System.Drawing.Point(66, 61); + this.tbIPRange.Name = "tbIPRange"; + this.tbIPRange.Size = new System.Drawing.Size(251, 20); + this.tbIPRange.TabIndex = 8; + // + // WF_IPScanner + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tbIPRange); + this.Controls.Add(this.btnScan); + this.Controls.Add(this.label1); + this.Controls.Add(this.lvIPList); + this.Name = "WF_IPScanner"; + this.Size = new System.Drawing.Size(800, 450); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ListView lvIPList; + private System.Windows.Forms.ColumnHeader chIP; + private System.Windows.Forms.ColumnHeader chPing; + private System.Windows.Forms.ColumnHeader chHost; + private System.Windows.Forms.ColumnHeader chRecognized; + private System.Windows.Forms.Button btnScan; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox tbIPRange; + } +} diff --git a/Networking/Pages/WF_IPScanner.cs b/Networking/Pages/WF_IPScanner.cs new file mode 100644 index 0000000..60ecab5 --- /dev/null +++ b/Networking/Pages/WF_IPScanner.cs @@ -0,0 +1,109 @@ +using IPScanner; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Networking.Pages +{ + public partial class WF_IPScanner : UserControl + { + NetworkScanner scanner = new NetworkScanner(); + Timer timer = new Timer(); + List<IPScanResult> results; + public WF_IPScanner() + { + InitializeComponent(); + List<Tuple<IPAddress, IPAddress>> ipRanges = IPRanges.GetOperationalIPRanges(); + if (ipRanges.Count > 0) + { + //tbIPRange.Text = ipRanges[0].Item1.GetAddressBytes().ToString(); + tbIPRange.Text = IPtoString(ipRanges[0].Item1.GetAddressBytes()) + "-" + ipRanges[0].Item2.GetAddressBytes()[3].ToString(); + // ipTo.IPAddress = ipRanges[0].Item2; + } + } + + string IPtoString(byte[] array) + { + // + // Concatenate all the elements into a StringBuilder. + // + StringBuilder strinbuilder = new StringBuilder(); + for (int i = 0; i < array.Count(); i++) + { + + strinbuilder.Append(array[i]); + if (i != array.Count() - 1) + strinbuilder.Append('.'); + } + return strinbuilder.ToString(); + } + + private string GetPingTime(IPScanResult result) + { + if (result.ping > -1) + return result.ping + " ms"; + return "N/A"; + } + + private void PopulateListView() + { + bool itemModified = false; + for (int i = 0; i < results.Count; i++) + { + IPScanResult result = results[i]; + if (result.status == ScanStatus.Complete || result.status == ScanStatus.Partial) + { + string ip = result.ip.ToString(); + ListViewItem[] matchedItems = lvIPList.Items.Find(ip, false); + if (matchedItems.Length > 0) + { + matchedItems[0].Tag = result.response; + matchedItems[0].SubItems[0].Text = result.ip.ToString(); + matchedItems[0].SubItems[1].Text = GetPingTime(result); + matchedItems[0].SubItems[2].Text = result.host; + matchedItems[0].SubItems[3].Text = result.identification; + } + else + { + ListViewItem lvi = new ListViewItem(new string[] { result.ip.ToString(), GetPingTime(result), result.host, result.identification }); + lvi.Name = ip; + lvIPList.Items.Add(lvi); + } + itemModified = true; + } + } + } + + + + + void timer_Tick(object sender, EventArgs e) + { + PopulateListView(); + } + + private void btnScan_Click(object sender, EventArgs e) + { + timer.Stop(); + lvIPList.Items.Clear(); + + IPAddress ipFrom = IPAddress.Parse(tbIPRange.Text.Split(new Char[] { '-' })[0]); + + IPAddress ipTo = IPAddress.Parse(ipFrom.GetAddressBytes()[0] + "." + + ipFrom.GetAddressBytes()[1] + "." + + ipFrom.GetAddressBytes()[2] + "." + + byte.Parse(tbIPRange.Text.Split(new Char[] { '-' })[1].ToString())); + results = scanner.BeginScan(ipFrom, ipTo); + timer.Interval = 1000; + timer.Tick += timer_Tick; + timer.Start(); + } + } +} diff --git a/Networking/Pages/WF_IPScanner.resx b/Networking/Pages/WF_IPScanner.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Networking/Pages/WF_IPScanner.resx @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/Networking/PluginInfo.cs b/Networking/PluginInfo.cs new file mode 100644 index 0000000..e70ed7f --- /dev/null +++ b/Networking/PluginInfo.cs @@ -0,0 +1,32 @@ +using Networking.Pages; +using PTConverter.Plugin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Networking +{ + public class PluginInfo : IPlugin + { + public string Author => "Kevin Krüger"; + + public string Company => ""; + + public string PluginName => "Networking"; + + public string Description => "This Plugin provides Network features."; + + public string Version => "1.0.0"; + + public string IconLink => null; + + public IEnumerable<IPage> RegisterPages => new List<IPage>() + { + {new IPv4() }, + {new PortScanner() }, + {new Pages.IPScanner() } + }; + } +} diff --git a/Networking/SmartThreadPool/CallerThreadContext.cs b/Networking/SmartThreadPool/CallerThreadContext.cs new file mode 100644 index 0000000..e63add5 --- /dev/null +++ b/Networking/SmartThreadPool/CallerThreadContext.cs @@ -0,0 +1,138 @@ + +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + +using System; +using System.Diagnostics; +using System.Threading; +using System.Reflection; +using System.Web; +using System.Runtime.Remoting.Messaging; + + +namespace Amib.Threading.Internal +{ +#region CallerThreadContext class + + /// <summary> + /// This class stores the caller call context in order to restore + /// it when the work item is executed in the thread pool environment. + /// </summary> + internal class CallerThreadContext + { +#region Prepare reflection information + + // Cached type information. + private static readonly MethodInfo getLogicalCallContextMethodInfo = + typeof(Thread).GetMethod("GetLogicalCallContext", BindingFlags.Instance | BindingFlags.NonPublic); + + private static readonly MethodInfo setLogicalCallContextMethodInfo = + typeof(Thread).GetMethod("SetLogicalCallContext", BindingFlags.Instance | BindingFlags.NonPublic); + + private static string HttpContextSlotName = GetHttpContextSlotName(); + + private static string GetHttpContextSlotName() + { + FieldInfo fi = typeof(HttpContext).GetField("CallContextSlotName", BindingFlags.Static | BindingFlags.NonPublic); + + if (fi != null) + { + return (string) fi.GetValue(null); + } + + return "HttpContext"; + } + + #endregion + +#region Private fields + + private HttpContext _httpContext; + private LogicalCallContext _callContext; + + #endregion + + /// <summary> + /// Constructor + /// </summary> + private CallerThreadContext() + { + } + + public bool CapturedCallContext + { + get + { + return (null != _callContext); + } + } + + public bool CapturedHttpContext + { + get + { + return (null != _httpContext); + } + } + + /// <summary> + /// Captures the current thread context + /// </summary> + /// <returns></returns> + public static CallerThreadContext Capture( + bool captureCallContext, + bool captureHttpContext) + { + Debug.Assert(captureCallContext || captureHttpContext); + + CallerThreadContext callerThreadContext = new CallerThreadContext(); + + // TODO: In NET 2.0, redo using the new feature of ExecutionContext class - Capture() + // Capture Call Context + if(captureCallContext && (getLogicalCallContextMethodInfo != null)) + { + callerThreadContext._callContext = (LogicalCallContext)getLogicalCallContextMethodInfo.Invoke(Thread.CurrentThread, null); + if (callerThreadContext._callContext != null) + { + callerThreadContext._callContext = (LogicalCallContext)callerThreadContext._callContext.Clone(); + } + } + + // Capture httpContext + if (captureHttpContext && (null != HttpContext.Current)) + { + callerThreadContext._httpContext = HttpContext.Current; + } + + return callerThreadContext; + } + + /// <summary> + /// Applies the thread context stored earlier + /// </summary> + /// <param name="callerThreadContext"></param> + public static void Apply(CallerThreadContext callerThreadContext) + { + if (null == callerThreadContext) + { + throw new ArgumentNullException("callerThreadContext"); + } + + // Todo: In NET 2.0, redo using the new feature of ExecutionContext class - Run() + // Restore call context + if ((callerThreadContext._callContext != null) && (setLogicalCallContextMethodInfo != null)) + { + setLogicalCallContextMethodInfo.Invoke(Thread.CurrentThread, new object[] { callerThreadContext._callContext }); + } + + // Restore HttpContext + if (callerThreadContext._httpContext != null) + { + HttpContext.Current = callerThreadContext._httpContext; + //CallContext.SetData(HttpContextSlotName, callerThreadContext._httpContext); + } + } + } + + #endregion +} +#endif diff --git a/Networking/SmartThreadPool/CanceledWorkItemsGroup.cs b/Networking/SmartThreadPool/CanceledWorkItemsGroup.cs new file mode 100644 index 0000000..5752957 --- /dev/null +++ b/Networking/SmartThreadPool/CanceledWorkItemsGroup.cs @@ -0,0 +1,14 @@ +namespace Amib.Threading.Internal +{ + internal class CanceledWorkItemsGroup + { + public readonly static CanceledWorkItemsGroup NotCanceledWorkItemsGroup = new CanceledWorkItemsGroup(); + + public CanceledWorkItemsGroup() + { + IsCanceled = false; + } + + public bool IsCanceled { get; set; } + } +} \ No newline at end of file diff --git a/Networking/SmartThreadPool/EventWaitHandle.cs b/Networking/SmartThreadPool/EventWaitHandle.cs new file mode 100644 index 0000000..25be07a --- /dev/null +++ b/Networking/SmartThreadPool/EventWaitHandle.cs @@ -0,0 +1,104 @@ +#if (_WINDOWS_CE) + +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Amib.Threading.Internal +{ + /// <summary> + /// EventWaitHandle class + /// In WindowsCE this class doesn't exist and I needed the WaitAll and WaitAny implementation. + /// So I wrote this class to implement these two methods with some of their overloads. + /// It uses the WaitForMultipleObjects API to do the WaitAll and WaitAny. + /// Note that this class doesn't even inherit from WaitHandle! + /// </summary> + public class STPEventWaitHandle + { + #region Public Constants + + public const int WaitTimeout = Timeout.Infinite; + + #endregion + + #region Private External Constants + + private const Int32 WAIT_FAILED = -1; + private const Int32 WAIT_TIMEOUT = 0x102; + private const UInt32 INFINITE = 0xFFFFFFFF; + + #endregion + + #region WaitAll and WaitAny + + internal static bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout, bool exitContext) + { + return waitHandle.WaitOne(millisecondsTimeout, exitContext); + } + + private static IntPtr[] PrepareNativeHandles(WaitHandle[] waitHandles) + { + IntPtr[] nativeHandles = new IntPtr[waitHandles.Length]; + for (int i = 0; i < waitHandles.Length; i++) + { + nativeHandles[i] = waitHandles[i].Handle; + } + return nativeHandles; + } + + public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + uint timeout = millisecondsTimeout < 0 ? INFINITE : (uint)millisecondsTimeout; + + IntPtr[] nativeHandles = PrepareNativeHandles(waitHandles); + + int result = WaitForMultipleObjects((uint)waitHandles.Length, nativeHandles, true, timeout); + + if (result == WAIT_TIMEOUT || result == WAIT_FAILED) + { + return false; + } + + return true; + } + + + public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + uint timeout = millisecondsTimeout < 0 ? INFINITE : (uint)millisecondsTimeout; + + IntPtr[] nativeHandles = PrepareNativeHandles(waitHandles); + + int result = WaitForMultipleObjects((uint)waitHandles.Length, nativeHandles, false, timeout); + + if (result >= 0 && result < waitHandles.Length) + { + return result; + } + + return -1; + } + + public static int WaitAny(WaitHandle[] waitHandles) + { + return WaitAny(waitHandles, Timeout.Infinite, false); + } + + public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout, bool exitContext) + { + int millisecondsTimeout = (int)timeout.TotalMilliseconds; + + return WaitAny(waitHandles, millisecondsTimeout, false); + } + + #endregion + + #region External methods + + [DllImport("coredll.dll", SetLastError = true)] + public static extern int WaitForMultipleObjects(uint nCount, IntPtr[] lpHandles, bool fWaitAll, uint dwMilliseconds); + + #endregion + } +} +#endif \ No newline at end of file diff --git a/Networking/SmartThreadPool/EventWaitHandleFactory.cs b/Networking/SmartThreadPool/EventWaitHandleFactory.cs new file mode 100644 index 0000000..3c9c849 --- /dev/null +++ b/Networking/SmartThreadPool/EventWaitHandleFactory.cs @@ -0,0 +1,82 @@ +using System.Threading; + +#if (_WINDOWS_CE) +using System; +using System.Runtime.InteropServices; +#endif + +namespace Amib.Threading.Internal +{ + /// <summary> + /// EventWaitHandleFactory class. + /// This is a static class that creates AutoResetEvent and ManualResetEvent objects. + /// In WindowCE the WaitForMultipleObjects API fails to use the Handle property + /// of XxxResetEvent. It can use only handles that were created by the CreateEvent API. + /// Consequently this class creates the needed XxxResetEvent and replaces the handle if + /// it's a WindowsCE OS. + /// </summary> + public static class EventWaitHandleFactory + { + /// <summary> + /// Create a new AutoResetEvent object + /// </summary> + /// <returns>Return a new AutoResetEvent object</returns> + public static AutoResetEvent CreateAutoResetEvent() + { + AutoResetEvent waitHandle = new AutoResetEvent(false); + +#if (_WINDOWS_CE) + ReplaceEventHandle(waitHandle, false, false); +#endif + + return waitHandle; + } + + /// <summary> + /// Create a new ManualResetEvent object + /// </summary> + /// <returns>Return a new ManualResetEvent object</returns> + public static ManualResetEvent CreateManualResetEvent(bool initialState) + { + ManualResetEvent waitHandle = new ManualResetEvent(initialState); + +#if (_WINDOWS_CE) + ReplaceEventHandle(waitHandle, true, initialState); +#endif + + return waitHandle; + } + +#if (_WINDOWS_CE) + + /// <summary> + /// Replace the event handle + /// </summary> + /// <param name="waitHandle">The WaitHandle object which its handle needs to be replaced.</param> + /// <param name="manualReset">Indicates if the event is a ManualResetEvent (true) or an AutoResetEvent (false)</param> + /// <param name="initialState">The initial state of the event</param> + private static void ReplaceEventHandle(WaitHandle waitHandle, bool manualReset, bool initialState) + { + // Store the old handle + IntPtr oldHandle = waitHandle.Handle; + + // Create a new event + IntPtr newHandle = CreateEvent(IntPtr.Zero, manualReset, initialState, null); + + // Replace the old event with the new event + waitHandle.Handle = newHandle; + + // Close the old event + CloseHandle (oldHandle); + } + + [DllImport("coredll.dll", SetLastError = true)] + public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName); + + //Handle + [DllImport("coredll.dll", SetLastError = true)] + public static extern bool CloseHandle(IntPtr hObject); +#endif + + } +} diff --git a/Networking/SmartThreadPool/Exceptions.cs b/Networking/SmartThreadPool/Exceptions.cs new file mode 100644 index 0000000..50b34af --- /dev/null +++ b/Networking/SmartThreadPool/Exceptions.cs @@ -0,0 +1,144 @@ +using System; +#if !(_WINDOWS_CE) +using System.Runtime.Serialization; +#endif + +namespace Amib.Threading +{ + #region Exceptions + + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been canceled + /// </summary> + public sealed partial class WorkItemCancelException : Exception + { + public WorkItemCancelException() + { + } + + public WorkItemCancelException(string message) + : base(message) + { + } + + public WorkItemCancelException(string message, Exception e) + : base(message, e) + { + } + } + + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// </summary> + public sealed partial class WorkItemTimeoutException : Exception + { + public WorkItemTimeoutException() + { + } + + public WorkItemTimeoutException(string message) + : base(message) + { + } + + public WorkItemTimeoutException(string message, Exception e) + : base(message, e) + { + } + } + + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// </summary> + public sealed partial class WorkItemResultException : Exception + { + public WorkItemResultException() + { + } + + public WorkItemResultException(string message) + : base(message) + { + } + + public WorkItemResultException(string message, Exception e) + : base(message, e) + { + } + } + + + /// <summary> + /// Represents an exception in case the STP queue is full and work item cannot be queued. + /// Relevant when the STP has a queue size limit + /// </summary> + public sealed partial class QueueRejectedException : Exception + { + public QueueRejectedException() + { + } + + public QueueRejectedException(string message) + : base(message) + { + } + + public QueueRejectedException(string message, Exception e) + : base(message, e) + { + } + } + +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been canceled + /// </summary> + [Serializable] + public sealed partial class WorkItemCancelException + { + public WorkItemCancelException(SerializationInfo si, StreamingContext sc) + : base(si, sc) + { + } + } + + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// </summary> + [Serializable] + public sealed partial class WorkItemTimeoutException + { + public WorkItemTimeoutException(SerializationInfo si, StreamingContext sc) + : base(si, sc) + { + } + } + + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// </summary> + [Serializable] + public sealed partial class WorkItemResultException + { + public WorkItemResultException(SerializationInfo si, StreamingContext sc) + : base(si, sc) + { + } + } + + /// <summary> + /// Represents an exception in case IWorkItemResult.GetResult has been timed out + /// </summary> + [Serializable] + public sealed partial class QueueRejectedException + { + public QueueRejectedException(SerializationInfo si, StreamingContext sc) + : base(si, sc) + { + } + } + +#endif + + #endregion +} diff --git a/Networking/SmartThreadPool/Interfaces.cs b/Networking/SmartThreadPool/Interfaces.cs new file mode 100644 index 0000000..f23205d --- /dev/null +++ b/Networking/SmartThreadPool/Interfaces.cs @@ -0,0 +1,603 @@ +using System; +using System.Threading; + +namespace Amib.Threading +{ + #region Delegates + + /// <summary> + /// A delegate that represents the method to run as the work item + /// </summary> + /// <param name="state">A state object for the method to run</param> + public delegate object WorkItemCallback(object state); + + /// <summary> + /// A delegate to call after the WorkItemCallback completed + /// </summary> + /// <param name="wir">The work item result object</param> + public delegate void PostExecuteWorkItemCallback(IWorkItemResult wir); + + /// <summary> + /// A delegate to call after the WorkItemCallback completed + /// </summary> + /// <param name="wir">The work item result object</param> + public delegate void PostExecuteWorkItemCallback<TResult>(IWorkItemResult<TResult> wir); + + /// <summary> + /// A delegate to call when a WorkItemsGroup becomes idle + /// </summary> + /// <param name="workItemsGroup">A reference to the WorkItemsGroup that became idle</param> + public delegate void WorkItemsGroupIdleHandler(IWorkItemsGroup workItemsGroup); + + /// <summary> + /// A delegate to call after a thread is created, but before + /// it's first use. + /// </summary> + public delegate void ThreadInitializationHandler(); + + /// <summary> + /// A delegate to call when a thread is about to exit, after + /// it is no longer belong to the pool. + /// </summary> + public delegate void ThreadTerminationHandler(); + + #endregion + + #region WorkItem Priority + + /// <summary> + /// Defines the availeable priorities of a work item. + /// The higher the priority a work item has, the sooner + /// it will be executed. + /// </summary> + public enum WorkItemPriority + { + Lowest, + BelowNormal, + Normal, + AboveNormal, + Highest, + } + + #endregion + + #region IWorkItemsGroup interface + + /// <summary> + /// IWorkItemsGroup interface + /// Created by SmartThreadPool.CreateWorkItemsGroup() + /// </summary> + public interface IWorkItemsGroup + { + /// <summary> + /// Get/Set the name of the WorkItemsGroup + /// </summary> + string Name { get; set; } + + /// <summary> + /// Get/Set the maximum number of workitem that execute cocurrency on the thread pool + /// </summary> + int Concurrency { get; set; } + + /// <summary> + /// Get the number of work items waiting in the queue. + /// </summary> + int WaitingCallbacks { get; } + + /// <summary> + /// Get the number of currently executing work items + /// </summary> + int InUseThreads { get; } + + /// <summary> + /// Get an array with all the state objects of the currently running items. + /// The array represents a snap shot and impact performance. + /// </summary> + object[] GetStates(); + + /// <summary> + /// Get the WorkItemsGroup start information + /// </summary> + WIGStartInfo WIGStartInfo { get; } + + /// <summary> + /// Starts to execute work items + /// </summary> + void Start(); + + /// <summary> + /// Cancel all the work items. + /// Same as Cancel(false) + /// </summary> + void Cancel(); + + /// <summary> + /// Cancel all work items using thread abortion + /// </summary> + /// <param name="abortExecution">True to stop work items by raising ThreadAbortException</param> + void Cancel(bool abortExecution); + + /// <summary> + /// Wait for all work item to complete. + /// </summary> + void WaitForIdle(); + + /// <summary> + /// Wait for all work item to complete, until timeout expired + /// </summary> + /// <param name="timeout">How long to wait for the work items to complete</param> + /// <returns>Returns true if work items completed within the timeout, otherwise false.</returns> + bool WaitForIdle(TimeSpan timeout); + + /// <summary> + /// Wait for all work item to complete, until timeout expired + /// </summary> + /// <param name="millisecondsTimeout">How long to wait for the work items to complete in milliseconds</param> + /// <returns>Returns true if work items completed within the timeout, otherwise false.</returns> + bool WaitForIdle(int millisecondsTimeout); + + /// <summary> + /// IsIdle is true when there are no work items running or queued. + /// </summary> + bool IsIdle { get; } + + /// <summary> + /// This event is fired when all work items are completed. + /// (When IsIdle changes to true) + /// This event only work on WorkItemsGroup. On SmartThreadPool + /// it throws the NotImplementedException. + /// </summary> + event WorkItemsGroupIdleHandler OnIdle; + + #region QueueWorkItem + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="workItemPriority">The priority of the work item</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, WorkItemPriority workItemPriority); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, WorkItemPriority workItemPriority); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback, WorkItemPriority workItemPriority); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="callToPostExecute">Indicates on which cases to call to the post execute callback</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback, CallToPostExecute callToPostExecute); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="callToPostExecute">Indicates on which cases to call to the post execute callback</param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, PostExecuteWorkItemCallback postExecuteWorkItemCallback, CallToPostExecute callToPostExecute, WorkItemPriority workItemPriority); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="workItemInfo">Work item info</param> + /// <param name="callback">A callback to execute</param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback); + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="workItemInfo">Work item information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <returns>Returns a work item result</returns> + IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback, object state); + + #endregion + + #region QueueWorkItem(Action<...>) + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult object, but its GetResult() will always return null</returns> + IWorkItemResult QueueWorkItem(Action action, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult object, but its GetResult() will always return null</returns> + IWorkItemResult QueueWorkItem<T>(Action<T> action, T arg, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult object, but its GetResult() will always return null</returns> + IWorkItemResult QueueWorkItem<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult object, but its GetResult() will always return null</returns> + IWorkItemResult QueueWorkItem<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult object, but its GetResult() will always return null</returns> + IWorkItemResult QueueWorkItem<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + #endregion + + #region QueueWorkItem(Func<...>) + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult<TResult> object. + /// its GetResult() returns a TResult object</returns> + IWorkItemResult<TResult> QueueWorkItem<TResult>(Func<TResult> func, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult<TResult> object. + /// its GetResult() returns a TResult object</returns> + IWorkItemResult<TResult> QueueWorkItem<T, TResult>(Func<T, TResult> func, T arg, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult<TResult> object. + /// its GetResult() returns a TResult object</returns> + IWorkItemResult<TResult> QueueWorkItem<T1, T2, TResult>(Func<T1, T2, TResult> func, T1 arg1, T2 arg2, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult<TResult> object. + /// its GetResult() returns a TResult object</returns> + IWorkItemResult<TResult> QueueWorkItem<T1, T2, T3, TResult>(Func<T1, T2, T3, TResult> func, T1 arg1, T2 arg2, T3 arg3, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + /// <summary> + /// Queue a work item. + /// </summary> + /// <returns>Returns a IWorkItemResult<TResult> object. + /// its GetResult() returns a TResult object</returns> + IWorkItemResult<TResult> QueueWorkItem<T1, T2, T3, T4, TResult>(Func<T1, T2, T3, T4, TResult> func, T1 arg1, T2 arg2, T3 arg3, T4 arg4, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority); + + #endregion + } + + #endregion + + #region CallToPostExecute enumerator + + [Flags] + public enum CallToPostExecute + { + /// <summary> + /// Never call to the PostExecute call back + /// </summary> + Never = 0x00, + + /// <summary> + /// Call to the PostExecute only when the work item is cancelled + /// </summary> + WhenWorkItemCanceled = 0x01, + + /// <summary> + /// Call to the PostExecute only when the work item is not cancelled + /// </summary> + WhenWorkItemNotCanceled = 0x02, + + /// <summary> + /// Always call to the PostExecute + /// </summary> + Always = WhenWorkItemCanceled | WhenWorkItemNotCanceled, + } + + #endregion + + #region IWorkItemResult interface + + /// <summary> + /// The common interface of IWorkItemResult and IWorkItemResult<T> + /// </summary> + public interface IWaitableResult + { + /// <summary> + /// This method intent is for internal use. + /// </summary> + /// <returns></returns> + IWorkItemResult GetWorkItemResult(); + + /// <summary> + /// This method intent is for internal use. + /// </summary> + /// <returns></returns> + IWorkItemResult<TResult> GetWorkItemResultT<TResult>(); + } + + /// <summary> + /// IWorkItemResult interface. + /// Created when a WorkItemCallback work item is queued. + /// </summary> + public interface IWorkItemResult : IWorkItemResult<object> + { + } + + /// <summary> + /// IWorkItemResult<TResult> interface. + /// Created when a Func<TResult> work item is queued. + /// </summary> + public interface IWorkItemResult<TResult> : IWaitableResult + { + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits. + /// </summary> + /// <returns>The result of the work item</returns> + TResult GetResult(); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// </summary> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + TResult GetResult( + int millisecondsTimeout, + bool exitContext); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// </summary> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + TResult GetResult( + TimeSpan timeout, + bool exitContext); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// </summary> + /// <param name="millisecondsTimeout">Timeout in milliseconds, or -1 for infinite</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the blocking if needed</param> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + TResult GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// </summary> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + TResult GetResult( + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits. + /// </summary> + /// <param name="e">Filled with the exception if one was thrown</param> + /// <returns>The result of the work item</returns> + TResult GetResult(out Exception e); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// </summary> + /// <param name="millisecondsTimeout"></param> + /// <param name="exitContext"></param> + /// <param name="e">Filled with the exception if one was thrown</param> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + TResult GetResult( + int millisecondsTimeout, + bool exitContext, + out Exception e); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout. + /// </summary> + /// <param name="exitContext"></param> + /// <param name="e">Filled with the exception if one was thrown</param> + /// <param name="timeout"></param> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + TResult GetResult( + TimeSpan timeout, + bool exitContext, + out Exception e); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// </summary> + /// <param name="millisecondsTimeout">Timeout in milliseconds, or -1 for infinite</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the blocking if needed</param> + /// <param name="e">Filled with the exception if one was thrown</param> + /// <returns>The result of the work item</returns> + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + TResult GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle, + out Exception e); + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits until timeout or until the cancelWaitHandle is signaled. + /// </summary> + /// <returns>The result of the work item</returns> + /// <param name="cancelWaitHandle"></param> + /// <param name="e">Filled with the exception if one was thrown</param> + /// <param name="timeout"></param> + /// <param name="exitContext"></param> + /// On timeout throws WorkItemTimeoutException + /// On cancel throws WorkItemCancelException + TResult GetResult( + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle, + out Exception e); + + /// <summary> + /// Gets an indication whether the asynchronous operation has completed. + /// </summary> + bool IsCompleted { get; } + + /// <summary> + /// Gets an indication whether the asynchronous operation has been canceled. + /// </summary> + bool IsCanceled { get; } + + /// <summary> + /// Gets the user-defined object that contains context data + /// for the work item method. + /// </summary> + object State { get; } + + /// <summary> + /// Same as Cancel(false). + /// </summary> + bool Cancel(); + + /// <summary> + /// Cancel the work item execution. + /// If the work item is in the queue then it won't execute + /// If the work item is completed, it will remain completed + /// If the work item is in progress then the user can check the SmartThreadPool.IsWorkItemCanceled + /// property to check if the work item has been cancelled. If the abortExecution is set to true then + /// the Smart Thread Pool will send an AbortException to the running thread to stop the execution + /// of the work item. When an in progress work item is canceled its GetResult will throw WorkItemCancelException. + /// If the work item is already cancelled it will remain cancelled + /// </summary> + /// <param name="abortExecution">When true send an AbortException to the executing thread.</param> + /// <returns>Returns true if the work item was not completed, otherwise false.</returns> + bool Cancel(bool abortExecution); + + /// <summary> + /// Get the work item's priority + /// </summary> + WorkItemPriority WorkItemPriority { get; } + + /// <summary> + /// Return the result, same as GetResult() + /// </summary> + TResult Result { get; } + + /// <summary> + /// Returns the exception if occured otherwise returns null. + /// </summary> + object Exception { get; } + } + + #endregion + + #region .NET 3.5 + + // All these delegate are built-in .NET 3.5 + // Comment/Remove them when compiling to .NET 3.5 to avoid ambiguity. + + public delegate void Action(); + public delegate void Action<T1, T2>(T1 arg1, T2 arg2); + public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3); + public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + + public delegate TResult Func<TResult>(); + public delegate TResult Func<T, TResult>(T arg1); + public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2); + public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3); + public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + + #endregion +} diff --git a/Networking/SmartThreadPool/InternalInterfaces.cs b/Networking/SmartThreadPool/InternalInterfaces.cs new file mode 100644 index 0000000..b1efa34 --- /dev/null +++ b/Networking/SmartThreadPool/InternalInterfaces.cs @@ -0,0 +1,27 @@ + +namespace Amib.Threading.Internal +{ + /// <summary> + /// An internal delegate to call when the WorkItem starts or completes + /// </summary> + internal delegate void WorkItemStateCallback(WorkItem workItem); + + internal interface IInternalWorkItemResult + { + event WorkItemStateCallback OnWorkItemStarted; + event WorkItemStateCallback OnWorkItemCompleted; + } + + internal interface IInternalWaitableResult + { + /// <summary> + /// This method is intent for internal use. + /// </summary> + IWorkItemResult GetWorkItemResult(); + } + + public interface IHasWorkItemPriority + { + WorkItemPriority WorkItemPriority { get; } + } +} diff --git a/Networking/SmartThreadPool/PriorityQueue.cs b/Networking/SmartThreadPool/PriorityQueue.cs new file mode 100644 index 0000000..409c879 --- /dev/null +++ b/Networking/SmartThreadPool/PriorityQueue.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Amib.Threading.Internal +{ + #region PriorityQueue class + + /// <summary> + /// PriorityQueue class + /// This class is not thread safe because we use external lock + /// </summary> + public sealed class PriorityQueue : IEnumerable + { + #region Private members + + /// <summary> + /// The number of queues, there is one for each type of priority + /// </summary> + private const int _queuesCount = WorkItemPriority.Highest-WorkItemPriority.Lowest+1; + + /// <summary> + /// Work items queues. There is one for each type of priority + /// </summary> + private readonly LinkedList<IHasWorkItemPriority>[] _queues = new LinkedList<IHasWorkItemPriority>[_queuesCount]; + + /// <summary> + /// The total number of work items within the queues + /// </summary> + private int _workItemsCount; + + /// <summary> + /// Use with IEnumerable interface + /// </summary> + private int _version; + + #endregion + + #region Contructor + + public PriorityQueue() + { + for(int i = 0; i < _queues.Length; ++i) + { + _queues[i] = new LinkedList<IHasWorkItemPriority>(); + } + } + + #endregion + + #region Methods + + /// <summary> + /// Enqueue a work item. + /// </summary> + /// <param name="workItem">A work item</param> + public void Enqueue(IHasWorkItemPriority workItem) + { + Debug.Assert(null != workItem); + + int queueIndex = _queuesCount-(int)workItem.WorkItemPriority-1; + Debug.Assert(queueIndex >= 0); + Debug.Assert(queueIndex < _queuesCount); + + _queues[queueIndex].AddLast(workItem); + ++_workItemsCount; + ++_version; + } + + /// <summary> + /// Dequeque a work item. + /// </summary> + /// <returns>Returns the next work item</returns> + public IHasWorkItemPriority Dequeue() + { + IHasWorkItemPriority workItem = null; + + if(_workItemsCount > 0) + { + int queueIndex = GetNextNonEmptyQueue(-1); + Debug.Assert(queueIndex >= 0); + workItem = _queues[queueIndex].First.Value; + _queues[queueIndex].RemoveFirst(); + Debug.Assert(null != workItem); + --_workItemsCount; + ++_version; + } + + return workItem; + } + + /// <summary> + /// Find the next non empty queue starting at queue queueIndex+1 + /// </summary> + /// <param name="queueIndex">The index-1 to start from</param> + /// <returns> + /// The index of the next non empty queue or -1 if all the queues are empty + /// </returns> + private int GetNextNonEmptyQueue(int queueIndex) + { + for(int i = queueIndex+1; i < _queuesCount; ++i) + { + if(_queues[i].Count > 0) + { + return i; + } + } + return -1; + } + + /// <summary> + /// The number of work items + /// </summary> + public int Count + { + get + { + return _workItemsCount; + } + } + + /// <summary> + /// Clear all the work items + /// </summary> + public void Clear() + { + if (_workItemsCount > 0) + { + foreach(LinkedList<IHasWorkItemPriority> queue in _queues) + { + queue.Clear(); + } + _workItemsCount = 0; + ++_version; + } + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator to iterate over the work items + /// </summary> + /// <returns>Returns an enumerator</returns> + public IEnumerator GetEnumerator() + { + return new PriorityQueueEnumerator(this); + } + + #endregion + + #region PriorityQueueEnumerator + + /// <summary> + /// The class the implements the enumerator + /// </summary> + private class PriorityQueueEnumerator : IEnumerator + { + private readonly PriorityQueue _priorityQueue; + private int _version; + private int _queueIndex; + private IEnumerator _enumerator; + + public PriorityQueueEnumerator(PriorityQueue priorityQueue) + { + _priorityQueue = priorityQueue; + _version = _priorityQueue._version; + _queueIndex = _priorityQueue.GetNextNonEmptyQueue(-1); + if (_queueIndex >= 0) + { + _enumerator = _priorityQueue._queues[_queueIndex].GetEnumerator(); + } + else + { + _enumerator = null; + } + } + + #region IEnumerator Members + + public void Reset() + { + _version = _priorityQueue._version; + _queueIndex = _priorityQueue.GetNextNonEmptyQueue(-1); + if (_queueIndex >= 0) + { + _enumerator = _priorityQueue._queues[_queueIndex].GetEnumerator(); + } + else + { + _enumerator = null; + } + } + + public object Current + { + get + { + Debug.Assert(null != _enumerator); + return _enumerator.Current; + } + } + + public bool MoveNext() + { + if (null == _enumerator) + { + return false; + } + + if(_version != _priorityQueue._version) + { + throw new InvalidOperationException("The collection has been modified"); + + } + if (!_enumerator.MoveNext()) + { + _queueIndex = _priorityQueue.GetNextNonEmptyQueue(_queueIndex); + if(-1 == _queueIndex) + { + return false; + } + _enumerator = _priorityQueue._queues[_queueIndex].GetEnumerator(); + _enumerator.MoveNext(); + return true; + } + return true; + } + + #endregion + } + + #endregion + } + + #endregion +} diff --git a/Networking/SmartThreadPool/SLExt.cs b/Networking/SmartThreadPool/SLExt.cs new file mode 100644 index 0000000..0b894c8 --- /dev/null +++ b/Networking/SmartThreadPool/SLExt.cs @@ -0,0 +1,16 @@ +#if _SILVERLIGHT + +using System.Threading; + +namespace Amib.Threading +{ + public enum ThreadPriority + { + Lowest, + BelowNormal, + Normal, + AboveNormal, + Highest, + } +} +#endif diff --git a/Networking/SmartThreadPool/STPEventWaitHandle.cs b/Networking/SmartThreadPool/STPEventWaitHandle.cs new file mode 100644 index 0000000..d3fb31b --- /dev/null +++ b/Networking/SmartThreadPool/STPEventWaitHandle.cs @@ -0,0 +1,62 @@ +#if !(_WINDOWS_CE) + +using System; +using System.Threading; + +namespace Amib.Threading.Internal +{ +#if _SILVERLIGHT || WINDOWS_PHONE + internal static class STPEventWaitHandle + { + public const int WaitTimeout = Timeout.Infinite; + + internal static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + return WaitHandle.WaitAll(waitHandles, millisecondsTimeout); + } + + internal static int WaitAny(WaitHandle[] waitHandles) + { + return WaitHandle.WaitAny(waitHandles); + } + + internal static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + return WaitHandle.WaitAny(waitHandles, millisecondsTimeout); + } + + internal static bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout, bool exitContext) + { + return waitHandle.WaitOne(millisecondsTimeout); + } + } +#else + internal static class STPEventWaitHandle + { + public const int WaitTimeout = Timeout.Infinite; + + internal static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + return WaitHandle.WaitAll(waitHandles, millisecondsTimeout, exitContext); + } + + internal static int WaitAny(WaitHandle[] waitHandles) + { + return WaitHandle.WaitAny(waitHandles); + } + + internal static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + return WaitHandle.WaitAny(waitHandles, millisecondsTimeout, exitContext); + } + + internal static bool WaitOne(WaitHandle waitHandle, int millisecondsTimeout, bool exitContext) + { + return waitHandle.WaitOne(millisecondsTimeout, exitContext); + } + } +#endif + +} + +#endif \ No newline at end of file diff --git a/Networking/SmartThreadPool/STPPerformanceCounter.cs b/Networking/SmartThreadPool/STPPerformanceCounter.cs new file mode 100644 index 0000000..0663d1d --- /dev/null +++ b/Networking/SmartThreadPool/STPPerformanceCounter.cs @@ -0,0 +1,448 @@ +using System; +using System.Diagnostics; +using System.Threading; + +namespace Amib.Threading +{ + public interface ISTPPerformanceCountersReader + { + long InUseThreads { get; } + long ActiveThreads { get; } + long WorkItemsQueued { get; } + long WorkItemsProcessed { get; } + } +} + +namespace Amib.Threading.Internal +{ + internal interface ISTPInstancePerformanceCounters : IDisposable + { + void Close(); + void SampleThreads(long activeThreads, long inUseThreads); + void SampleWorkItems(long workItemsQueued, long workItemsProcessed); + void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime); + void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime); + } +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + + internal enum STPPerformanceCounterType + { + // Fields + ActiveThreads = 0, + InUseThreads = 1, + OverheadThreads = 2, + OverheadThreadsPercent = 3, + OverheadThreadsPercentBase = 4, + + WorkItems = 5, + WorkItemsInQueue = 6, + WorkItemsProcessed = 7, + + WorkItemsQueuedPerSecond = 8, + WorkItemsProcessedPerSecond = 9, + + AvgWorkItemWaitTime = 10, + AvgWorkItemWaitTimeBase = 11, + + AvgWorkItemProcessTime = 12, + AvgWorkItemProcessTimeBase = 13, + + WorkItemsGroups = 14, + + LastCounter = 14, + } + + + /// <summary> + /// Summary description for STPPerformanceCounter. + /// </summary> + internal class STPPerformanceCounter + { + // Fields + private readonly PerformanceCounterType _pcType; + protected string _counterHelp; + protected string _counterName; + + // Methods + public STPPerformanceCounter( + string counterName, + string counterHelp, + PerformanceCounterType pcType) + { + _counterName = counterName; + _counterHelp = counterHelp; + _pcType = pcType; + } + + public void AddCounterToCollection(CounterCreationDataCollection counterData) + { + CounterCreationData counterCreationData = new CounterCreationData( + _counterName, + _counterHelp, + _pcType); + + counterData.Add(counterCreationData); + } + + // Properties + public string Name + { + get + { + return _counterName; + } + } + } + + internal class STPPerformanceCounters + { + // Fields + internal STPPerformanceCounter[] _stpPerformanceCounters; + private static readonly STPPerformanceCounters _instance; + internal const string _stpCategoryHelp = "SmartThreadPool performance counters"; + internal const string _stpCategoryName = "SmartThreadPool"; + + // Methods + static STPPerformanceCounters() + { + _instance = new STPPerformanceCounters(); + } + + private STPPerformanceCounters() + { + STPPerformanceCounter[] stpPerformanceCounters = new STPPerformanceCounter[] + { + new STPPerformanceCounter("Active threads", "The current number of available in the thread pool.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("In use threads", "The current number of threads that execute a work item.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("Overhead threads", "The current number of threads that are active, but are not in use.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("% overhead threads", "The current number of threads that are active, but are not in use in percents.", PerformanceCounterType.RawFraction), + new STPPerformanceCounter("% overhead threads base", "The current number of threads that are active, but are not in use in percents.", PerformanceCounterType.RawBase), + + new STPPerformanceCounter("Work Items", "The number of work items in the Smart Thread Pool. Both queued and processed.", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("Work Items in queue", "The current number of work items in the queue", PerformanceCounterType.NumberOfItems32), + new STPPerformanceCounter("Work Items processed", "The number of work items already processed", PerformanceCounterType.NumberOfItems32), + + new STPPerformanceCounter("Work Items queued/sec", "The number of work items queued per second", PerformanceCounterType.RateOfCountsPerSecond32), + new STPPerformanceCounter("Work Items processed/sec", "The number of work items processed per second", PerformanceCounterType.RateOfCountsPerSecond32), + + new STPPerformanceCounter("Avg. Work Item wait time/sec", "The average time a work item supends in the queue waiting for its turn to execute.", PerformanceCounterType.AverageCount64), + new STPPerformanceCounter("Avg. Work Item wait time base", "The average time a work item supends in the queue waiting for its turn to execute.", PerformanceCounterType.AverageBase), + + new STPPerformanceCounter("Avg. Work Item process time/sec", "The average time it takes to process a work item.", PerformanceCounterType.AverageCount64), + new STPPerformanceCounter("Avg. Work Item process time base", "The average time it takes to process a work item.", PerformanceCounterType.AverageBase), + + new STPPerformanceCounter("Work Items Groups", "The current number of work item groups associated with the Smart Thread Pool.", PerformanceCounterType.NumberOfItems32), + }; + + _stpPerformanceCounters = stpPerformanceCounters; + SetupCategory(); + } + + private void SetupCategory() + { + if (!PerformanceCounterCategory.Exists(_stpCategoryName)) + { + CounterCreationDataCollection counters = new CounterCreationDataCollection(); + + for (int i = 0; i < _stpPerformanceCounters.Length; i++) + { + _stpPerformanceCounters[i].AddCounterToCollection(counters); + } + + PerformanceCounterCategory.Create( + _stpCategoryName, + _stpCategoryHelp, + PerformanceCounterCategoryType.MultiInstance, + counters); + + } + } + + // Properties + public static STPPerformanceCounters Instance + { + get + { + return _instance; + } + } + } + + internal class STPInstancePerformanceCounter : IDisposable + { + // Fields + private bool _isDisposed; + private PerformanceCounter _pcs; + + // Methods + protected STPInstancePerformanceCounter() + { + _isDisposed = false; + } + + public STPInstancePerformanceCounter( + string instance, + STPPerformanceCounterType spcType) : this() + { + STPPerformanceCounters counters = STPPerformanceCounters.Instance; + _pcs = new PerformanceCounter( + STPPerformanceCounters._stpCategoryName, + counters._stpPerformanceCounters[(int) spcType].Name, + instance, + false); + _pcs.RawValue = _pcs.RawValue; + } + + + public void Close() + { + if (_pcs != null) + { + _pcs.RemoveInstance(); + _pcs.Close(); + _pcs = null; + } + } + + public void Dispose() + { + Dispose(true); + } + + public virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + Close(); + } + } + _isDisposed = true; + } + + public virtual void Increment() + { + _pcs.Increment(); + } + + public virtual void IncrementBy(long val) + { + _pcs.IncrementBy(val); + } + + public virtual void Set(long val) + { + _pcs.RawValue = val; + } + } + + internal class STPInstanceNullPerformanceCounter : STPInstancePerformanceCounter + { + // Methods + public override void Increment() {} + public override void IncrementBy(long value) {} + public override void Set(long val) {} + } + + + + internal class STPInstancePerformanceCounters : ISTPInstancePerformanceCounters + { + private bool _isDisposed; + // Fields + private STPInstancePerformanceCounter[] _pcs; + private static readonly STPInstancePerformanceCounter _stpInstanceNullPerformanceCounter; + + // Methods + static STPInstancePerformanceCounters() + { + _stpInstanceNullPerformanceCounter = new STPInstanceNullPerformanceCounter(); + } + + public STPInstancePerformanceCounters(string instance) + { + _isDisposed = false; + _pcs = new STPInstancePerformanceCounter[(int)STPPerformanceCounterType.LastCounter]; + + // Call the STPPerformanceCounters.Instance so the static constructor will + // intialize the STPPerformanceCounters singleton. + STPPerformanceCounters.Instance.GetHashCode(); + + for (int i = 0; i < _pcs.Length; i++) + { + if (instance != null) + { + _pcs[i] = new STPInstancePerformanceCounter( + instance, + (STPPerformanceCounterType) i); + } + else + { + _pcs[i] = _stpInstanceNullPerformanceCounter; + } + } + } + + + public void Close() + { + if (null != _pcs) + { + for (int i = 0; i < _pcs.Length; i++) + { + if (null != _pcs[i]) + { + _pcs[i].Dispose(); + } + } + _pcs = null; + } + } + + public void Dispose() + { + Dispose(true); + } + + public virtual void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + { + Close(); + } + } + _isDisposed = true; + } + + private STPInstancePerformanceCounter GetCounter(STPPerformanceCounterType spcType) + { + return _pcs[(int) spcType]; + } + + public void SampleThreads(long activeThreads, long inUseThreads) + { + GetCounter(STPPerformanceCounterType.ActiveThreads).Set(activeThreads); + GetCounter(STPPerformanceCounterType.InUseThreads).Set(inUseThreads); + GetCounter(STPPerformanceCounterType.OverheadThreads).Set(activeThreads-inUseThreads); + + GetCounter(STPPerformanceCounterType.OverheadThreadsPercentBase).Set(activeThreads-inUseThreads); + GetCounter(STPPerformanceCounterType.OverheadThreadsPercent).Set(inUseThreads); + } + + public void SampleWorkItems(long workItemsQueued, long workItemsProcessed) + { + GetCounter(STPPerformanceCounterType.WorkItems).Set(workItemsQueued+workItemsProcessed); + GetCounter(STPPerformanceCounterType.WorkItemsInQueue).Set(workItemsQueued); + GetCounter(STPPerformanceCounterType.WorkItemsProcessed).Set(workItemsProcessed); + + GetCounter(STPPerformanceCounterType.WorkItemsQueuedPerSecond).Set(workItemsQueued); + GetCounter(STPPerformanceCounterType.WorkItemsProcessedPerSecond).Set(workItemsProcessed); + } + + public void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime) + { + GetCounter(STPPerformanceCounterType.AvgWorkItemWaitTime).IncrementBy((long)workItemWaitTime.TotalMilliseconds); + GetCounter(STPPerformanceCounterType.AvgWorkItemWaitTimeBase).Increment(); + } + + public void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime) + { + GetCounter(STPPerformanceCounterType.AvgWorkItemProcessTime).IncrementBy((long)workItemProcessTime.TotalMilliseconds); + GetCounter(STPPerformanceCounterType.AvgWorkItemProcessTimeBase).Increment(); + } + } +#endif + + internal class NullSTPInstancePerformanceCounters : ISTPInstancePerformanceCounters, ISTPPerformanceCountersReader + { + private static readonly NullSTPInstancePerformanceCounters _instance = new NullSTPInstancePerformanceCounters(); + + public static NullSTPInstancePerformanceCounters Instance + { + get { return _instance; } + } + + public void Close() {} + public void Dispose() {} + + public void SampleThreads(long activeThreads, long inUseThreads) {} + public void SampleWorkItems(long workItemsQueued, long workItemsProcessed) {} + public void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime) {} + public void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime) {} + public long InUseThreads + { + get { return 0; } + } + + public long ActiveThreads + { + get { return 0; } + } + + public long WorkItemsQueued + { + get { return 0; } + } + + public long WorkItemsProcessed + { + get { return 0; } + } + } + + internal class LocalSTPInstancePerformanceCounters : ISTPInstancePerformanceCounters, ISTPPerformanceCountersReader + { + public void Close() { } + public void Dispose() { } + + private long _activeThreads; + private long _inUseThreads; + private long _workItemsQueued; + private long _workItemsProcessed; + + public long InUseThreads + { + get { return _inUseThreads; } + } + + public long ActiveThreads + { + get { return _activeThreads; } + } + + public long WorkItemsQueued + { + get { return _workItemsQueued; } + } + + public long WorkItemsProcessed + { + get { return _workItemsProcessed; } + } + + public void SampleThreads(long activeThreads, long inUseThreads) + { + _activeThreads = activeThreads; + _inUseThreads = inUseThreads; + } + + public void SampleWorkItems(long workItemsQueued, long workItemsProcessed) + { + _workItemsQueued = workItemsQueued; + _workItemsProcessed = workItemsProcessed; + } + + public void SampleWorkItemsWaitTime(TimeSpan workItemWaitTime) + { + // Not supported + } + + public void SampleWorkItemsProcessTime(TimeSpan workItemProcessTime) + { + // Not supported + } + } +} diff --git a/Networking/SmartThreadPool/STPStartInfo.cs b/Networking/SmartThreadPool/STPStartInfo.cs new file mode 100644 index 0000000..4c7bc29 --- /dev/null +++ b/Networking/SmartThreadPool/STPStartInfo.cs @@ -0,0 +1,235 @@ +using System; +using System.Threading; + +namespace Amib.Threading +{ + /// <summary> + /// Summary description for STPStartInfo. + /// </summary> + public class STPStartInfo : WIGStartInfo + { + private int _idleTimeout = SmartThreadPool.DefaultIdleTimeout; + private int _minWorkerThreads = SmartThreadPool.DefaultMinWorkerThreads; + private int _maxWorkerThreads = SmartThreadPool.DefaultMaxWorkerThreads; +#if !(WINDOWS_PHONE) + private ThreadPriority _threadPriority = SmartThreadPool.DefaultThreadPriority; +#endif + private string _performanceCounterInstanceName = SmartThreadPool.DefaultPerformanceCounterInstanceName; + private bool _areThreadsBackground = SmartThreadPool.DefaultAreThreadsBackground; + private bool _enableLocalPerformanceCounters; + private string _threadPoolName = SmartThreadPool.DefaultThreadPoolName; + private int? _maxStackSize = SmartThreadPool.DefaultMaxStackSize; + private int? _maxQueueLength = SmartThreadPool.DefaultMaxQueueLength; + + public STPStartInfo() + { + _performanceCounterInstanceName = SmartThreadPool.DefaultPerformanceCounterInstanceName; +#if !(WINDOWS_PHONE) + _threadPriority = SmartThreadPool.DefaultThreadPriority; +#endif + _maxWorkerThreads = SmartThreadPool.DefaultMaxWorkerThreads; + _idleTimeout = SmartThreadPool.DefaultIdleTimeout; + _minWorkerThreads = SmartThreadPool.DefaultMinWorkerThreads; + } + + public STPStartInfo(STPStartInfo stpStartInfo) + : base(stpStartInfo) + { + _idleTimeout = stpStartInfo.IdleTimeout; + _minWorkerThreads = stpStartInfo.MinWorkerThreads; + _maxWorkerThreads = stpStartInfo.MaxWorkerThreads; +#if !(WINDOWS_PHONE) + _threadPriority = stpStartInfo.ThreadPriority; +#endif + _performanceCounterInstanceName = stpStartInfo.PerformanceCounterInstanceName; + _enableLocalPerformanceCounters = stpStartInfo._enableLocalPerformanceCounters; + _threadPoolName = stpStartInfo._threadPoolName; + _areThreadsBackground = stpStartInfo.AreThreadsBackground; + _maxQueueLength = stpStartInfo.MaxQueueLength; +#if !(_SILVERLIGHT) && !(WINDOWS_PHONE) + _apartmentState = stpStartInfo._apartmentState; +#endif + _maxStackSize = stpStartInfo._maxStackSize; + } + + /// <summary> + /// Get/Set the idle timeout in milliseconds. + /// If a thread is idle (starved) longer than IdleTimeout then it may quit. + /// </summary> + public virtual int IdleTimeout + { + get { return _idleTimeout; } + set + { + ThrowIfReadOnly(); + _idleTimeout = value; + } + } + + + /// <summary> + /// Get/Set the lower limit of threads in the pool. + /// </summary> + public virtual int MinWorkerThreads + { + get { return _minWorkerThreads; } + set + { + ThrowIfReadOnly(); + _minWorkerThreads = value; + } + } + + + /// <summary> + /// Get/Set the upper limit of threads in the pool. + /// </summary> + public virtual int MaxWorkerThreads + { + get { return _maxWorkerThreads; } + set + { + ThrowIfReadOnly(); + _maxWorkerThreads = value; + } + } + +#if !(WINDOWS_PHONE) + /// <summary> + /// Get/Set the scheduling priority of the threads in the pool. + /// The Os handles the scheduling. + /// </summary> + public virtual ThreadPriority ThreadPriority + { + get { return _threadPriority; } + set + { + ThrowIfReadOnly(); + _threadPriority = value; + } + } +#endif + /// <summary> + /// Get/Set the thread pool name. Threads will get names depending on this. + /// </summary> + public virtual string ThreadPoolName { + get { return _threadPoolName; } + set + { + ThrowIfReadOnly (); + _threadPoolName = value; + } + } + + /// <summary> + /// Get/Set the performance counter instance name of this SmartThreadPool + /// The default is null which indicate not to use performance counters at all. + /// </summary> + public virtual string PerformanceCounterInstanceName + { + get { return _performanceCounterInstanceName; } + set + { + ThrowIfReadOnly(); + _performanceCounterInstanceName = value; + } + } + + /// <summary> + /// Enable/Disable the local performance counter. + /// This enables the user to get some performance information about the SmartThreadPool + /// without using Windows performance counters. (Useful on WindowsCE, Silverlight, etc.) + /// The default is false. + /// </summary> + public virtual bool EnableLocalPerformanceCounters + { + get { return _enableLocalPerformanceCounters; } + set + { + ThrowIfReadOnly(); + _enableLocalPerformanceCounters = value; + } + } + + /// <summary> + /// Get/Set backgroundness of thread in thread pool. + /// </summary> + public virtual bool AreThreadsBackground + { + get { return _areThreadsBackground; } + set + { + ThrowIfReadOnly (); + _areThreadsBackground = value; + } + } + + /// <summary> + /// The maximum number of items allowed in the queue. Items attempting to be queued + /// when the queue is at its maximum will throw a QueueRejectedException. + /// + /// Value must be > 0. A <code>null</code> value will leave the queue unbounded (i.e. + /// bounded only by available resources). + /// + /// Ignored when <code>Enqueue()</code>ing on a Thread Pool from within a + /// <code>WorkItemsGroup</code>. + /// </summary> + public virtual int? MaxQueueLength + { + get { return _maxQueueLength; } + set + { + ThrowIfReadOnly(); + _maxQueueLength = value; + } + } + + /// <summary> + /// Get a readonly version of this STPStartInfo. + /// </summary> + /// <returns>Returns a readonly reference to this STPStartInfo</returns> + public new STPStartInfo AsReadOnly() + { + return new STPStartInfo(this) { _readOnly = true }; + } + +#if !(_SILVERLIGHT) && !(WINDOWS_PHONE) + + private ApartmentState _apartmentState = SmartThreadPool.DefaultApartmentState; + + /// <summary> + /// Get/Set the apartment state of threads in the thread pool + /// </summary> + public ApartmentState ApartmentState + { + get { return _apartmentState; } + set + { + ThrowIfReadOnly(); + _apartmentState = value; + } + } + +#if !(_SILVERLIGHT) && !(WINDOWS_PHONE) + + /// <summary> + /// Get/Set the max stack size of threads in the thread pool + /// </summary> + public int? MaxStackSize + { + get { return _maxStackSize; } + set + { + ThrowIfReadOnly(); + if (value.HasValue && value.Value < 0) + { + throw new ArgumentOutOfRangeException("value", "Value must be greater than 0."); + } + _maxStackSize = value; + } + } +#endif + +#endif + } +} diff --git a/Networking/SmartThreadPool/SmartThreadPool.ThreadEntry.cs b/Networking/SmartThreadPool/SmartThreadPool.ThreadEntry.cs new file mode 100644 index 0000000..4f49169 --- /dev/null +++ b/Networking/SmartThreadPool/SmartThreadPool.ThreadEntry.cs @@ -0,0 +1,60 @@ + +using System; +using Amib.Threading.Internal; + +namespace Amib.Threading +{ + public partial class SmartThreadPool + { + #region ThreadEntry class + + internal class ThreadEntry + { + /// <summary> + /// The thread creation time + /// The value is stored as UTC value. + /// </summary> + private readonly DateTime _creationTime; + + /// <summary> + /// The last time this thread has been running + /// It is updated by IAmAlive() method + /// The value is stored as UTC value. + /// </summary> + private DateTime _lastAliveTime; + + /// <summary> + /// A reference from each thread in the thread pool to its SmartThreadPool + /// object container. + /// With this variable a thread can know whatever it belongs to a + /// SmartThreadPool. + /// </summary> + private readonly SmartThreadPool _associatedSmartThreadPool; + + /// <summary> + /// A reference to the current work item a thread from the thread pool + /// is executing. + /// </summary> + public WorkItem CurrentWorkItem { get; set; } + + public ThreadEntry(SmartThreadPool stp) + { + _associatedSmartThreadPool = stp; + _creationTime = DateTime.UtcNow; + _lastAliveTime = DateTime.MinValue; + } + + public SmartThreadPool AssociatedSmartThreadPool + { + get { return _associatedSmartThreadPool; } + } + + public void IAmAlive() + { + _lastAliveTime = DateTime.UtcNow; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Networking/SmartThreadPool/SmartThreadPool.cs b/Networking/SmartThreadPool/SmartThreadPool.cs new file mode 100644 index 0000000..fb0e579 --- /dev/null +++ b/Networking/SmartThreadPool/SmartThreadPool.cs @@ -0,0 +1,1811 @@ +#region Release History + +// Smart Thread Pool +// 7 Aug 2004 - Initial release +// +// 14 Sep 2004 - Bug fixes +// +// 15 Oct 2004 - Added new features +// - Work items return result. +// - Support waiting synchronization for multiple work items. +// - Work items can be cancelled. +// - Passage of the caller thread’s context to the thread in the pool. +// - Minimal usage of WIN32 handles. +// - Minor bug fixes. +// +// 26 Dec 2004 - Changes: +// - Removed static constructors. +// - Added finalizers. +// - Changed Exceptions so they are serializable. +// - Fixed the bug in one of the SmartThreadPool constructors. +// - Changed the SmartThreadPool.WaitAll() so it will support any number of waiters. +// The SmartThreadPool.WaitAny() is still limited by the .NET Framework. +// - Added PostExecute with options on which cases to call it. +// - Added option to dispose of the state objects. +// - Added a WaitForIdle() method that waits until the work items queue is empty. +// - Added an STPStartInfo class for the initialization of the thread pool. +// - Changed exception handling so if a work item throws an exception it +// is rethrown at GetResult(), rather then firing an UnhandledException event. +// Note that PostExecute exception are always ignored. +// +// 25 Mar 2005 - Changes: +// - Fixed lost of work items bug +// +// 3 Jul 2005: Changes. +// - Fixed bug where Enqueue() throws an exception because PopWaiter() returned null, hardly reconstructed. +// +// 16 Aug 2005: Changes. +// - Fixed bug where the InUseThreads becomes negative when canceling work items. +// +// 31 Jan 2006 - Changes: +// - Added work items priority +// - Removed support of chained delegates in callbacks and post executes (nobody really use this) +// - Added work items groups +// - Added work items groups idle event +// - Changed SmartThreadPool.WaitAll() behavior so when it gets empty array +// it returns true rather then throwing an exception. +// - Added option to start the STP and the WIG as suspended +// - Exception behavior changed, the real exception is returned by an +// inner exception +// - Added option to keep the Http context of the caller thread. (Thanks to Steven T.) +// - Added performance counters +// - Added priority to the threads in the pool +// +// 13 Feb 2006 - Changes: +// - Added a call to the dispose of the Performance Counter so +// their won't be a Performance Counter leak. +// - Added exception catch in case the Performance Counters cannot +// be created. +// +// 17 May 2008 - Changes: +// - Changed the dispose behavior and removed the Finalizers. +// - Enabled the change of the MaxThreads and MinThreads at run time. +// - Enabled the change of the Concurrency of a IWorkItemsGroup at run +// time If the IWorkItemsGroup is a SmartThreadPool then the Concurrency +// refers to the MaxThreads. +// - Improved the cancel behavior. +// - Added events for thread creation and termination. +// - Fixed the HttpContext context capture. +// - Changed internal collections so they use generic collections +// - Added IsIdle flag to the SmartThreadPool and IWorkItemsGroup +// - Added support for WinCE +// - Added support for Action<T> and Func<T> +// +// 07 April 2009 - Changes: +// - Added support for Silverlight and Mono +// - Added Join, Choice, and Pipe to SmartThreadPool. +// - Added local performance counters (for Mono, Silverlight, and WindowsCE) +// - Changed duration measures from DateTime.Now to Stopwatch. +// - Queues changed from System.Collections.Queue to System.Collections.Generic.LinkedList<T>. +// +// 21 December 2009 - Changes: +// - Added work item timeout (passive) +// +// 20 August 2012 - Changes: +// - Added set name to threads +// - Fixed the WorkItemsQueue.Dequeue. +// Replaced while (!Monitor.TryEnter(this)); with lock(this) { ... } +// - Fixed SmartThreadPool.Pipe +// - Added IsBackground option to threads +// - Added ApartmentState to threads +// - Fixed thread creation when queuing many work items at the same time. +// +// 24 August 2012 - Changes: +// - Enabled cancel abort after cancel. See: http://smartthreadpool.codeplex.com/discussions/345937 by alecswan +// - Added option to set MaxStackSize of threads +// +// 16 September 2016 - Changes: +// - Separated the STP project to .NET 2.0, .NET 4.0, and .NET 4.5 +// - Added option to set MaxQueueLength (Thanks to Rob Hruska) + +#endregion + +using System; +using System.Security; +using System.Threading; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using Amib.Threading.Internal; + +namespace Amib.Threading +{ + #region SmartThreadPool class + /// <summary> + /// Smart thread pool class. + /// </summary> + public partial class SmartThreadPool : WorkItemsGroupBase, IDisposable + { + #region Public Default Constants + + /// <summary> + /// Default minimum number of threads the thread pool contains. (0) + /// </summary> + public const int DefaultMinWorkerThreads = 0; + + /// <summary> + /// Default maximum number of threads the thread pool contains. (25) + /// </summary> + public const int DefaultMaxWorkerThreads = 25; + + /// <summary> + /// Default idle timeout in milliseconds. (One minute) + /// </summary> + public const int DefaultIdleTimeout = 60*1000; // One minute + + /// <summary> + /// Indicate to copy the security context of the caller and then use it in the call. (false) + /// </summary> + public const bool DefaultUseCallerCallContext = false; + + /// <summary> + /// Indicate to copy the HTTP context of the caller and then use it in the call. (false) + /// </summary> + public const bool DefaultUseCallerHttpContext = false; + + /// <summary> + /// Indicate to dispose of the state objects if they support the IDispose interface. (false) + /// </summary> + public const bool DefaultDisposeOfStateObjects = false; + + /// <summary> + /// The default option to run the post execute (CallToPostExecute.Always) + /// </summary> + public const CallToPostExecute DefaultCallToPostExecute = CallToPostExecute.Always; + + /// <summary> + /// The default post execute method to run. (None) + /// When null it means not to call it. + /// </summary> + public static readonly PostExecuteWorkItemCallback DefaultPostExecuteWorkItemCallback; + + /// <summary> + /// The default work item priority (WorkItemPriority.Normal) + /// </summary> + public const WorkItemPriority DefaultWorkItemPriority = WorkItemPriority.Normal; + + /// <summary> + /// The default is to work on work items as soon as they arrive + /// and not to wait for the start. (false) + /// </summary> + public const bool DefaultStartSuspended = false; + + /// <summary> + /// The default name to use for the performance counters instance. (null) + /// </summary> + public static readonly string DefaultPerformanceCounterInstanceName; + +#if !(WINDOWS_PHONE) + + /// <summary> + /// The default thread priority (ThreadPriority.Normal) + /// </summary> + public const ThreadPriority DefaultThreadPriority = ThreadPriority.Normal; +#endif + /// <summary> + /// The default thread pool name. (SmartThreadPool) + /// </summary> + public const string DefaultThreadPoolName = "SmartThreadPool"; + + /// <summary> + /// The default Max Stack Size. (null) + /// </summary> + public static readonly int? DefaultMaxStackSize = null; + + /// <summary> + /// The default Max Queue Length (null). + /// </summary> + public static readonly int? DefaultMaxQueueLength = null; + + /// <summary> + /// The default fill state with params. (false) + /// It is relevant only to QueueWorkItem of Action<...>/Func<...> + /// </summary> + public const bool DefaultFillStateWithArgs = false; + + /// <summary> + /// The default thread backgroundness. (true) + /// </summary> + public const bool DefaultAreThreadsBackground = true; + +#if !(_SILVERLIGHT) && !(WINDOWS_PHONE) + /// <summary> + /// The default apartment state of a thread in the thread pool. + /// The default is ApartmentState.Unknown which means the STP will not + /// set the apartment of the thread. It will use the .NET default. + /// </summary> + public const ApartmentState DefaultApartmentState = ApartmentState.Unknown; +#endif + + #endregion + + #region Member Variables + + /// <summary> + /// Dictionary of all the threads in the thread pool. + /// </summary> + private readonly SynchronizedDictionary<Thread, ThreadEntry> _workerThreads = new SynchronizedDictionary<Thread, ThreadEntry>(); + + /// <summary> + /// Queue of work items. + /// </summary> + private readonly WorkItemsQueue _workItemsQueue = new WorkItemsQueue(); + + /// <summary> + /// Count the work items handled. + /// Used by the performance counter. + /// </summary> + private int _workItemsProcessed; + + /// <summary> + /// Number of threads that currently work (not idle). + /// </summary> + private int _inUseWorkerThreads; + + /// <summary> + /// Stores a copy of the original STPStartInfo. + /// It is used to change the MinThread and MaxThreads + /// </summary> + private STPStartInfo _stpStartInfo; + + /// <summary> + /// Total number of work items that are stored in the work items queue + /// plus the work items that the threads in the pool are working on. + /// </summary> + private volatile int _currentWorkItemsCount; + + /// <summary> + /// Signaled when the thread pool is idle, i.e. no thread is busy + /// and the work items queue is empty + /// </summary> + //private ManualResetEvent _isIdleWaitHandle = new ManualResetEvent(true); + private ManualResetEvent _isIdleWaitHandle = EventWaitHandleFactory.CreateManualResetEvent(true); + + /// <summary> + /// An event to signal all the threads to quit immediately. + /// </summary> + //private ManualResetEvent _shuttingDownEvent = new ManualResetEvent(false); + private ManualResetEvent _shuttingDownEvent = EventWaitHandleFactory.CreateManualResetEvent(false); + + /// <summary> + /// A flag to indicate if the Smart Thread Pool is now suspended. + /// </summary> + private bool _isSuspended; + + /// <summary> + /// A flag to indicate the threads to quit. + /// </summary> + private bool _shutdown; + + /// <summary> + /// Counts the threads created in the pool. + /// It is used to name the threads. + /// </summary> + private int _threadCounter; + + /// <summary> + /// Indicate that the SmartThreadPool has been disposed + /// </summary> + private bool _isDisposed; + + /// <summary> + /// Holds all the WorkItemsGroup instaces that have at least one + /// work item int the SmartThreadPool + /// This variable is used in case of Shutdown + /// </summary> + private readonly SynchronizedDictionary<IWorkItemsGroup, IWorkItemsGroup> _workItemsGroups = new SynchronizedDictionary<IWorkItemsGroup, IWorkItemsGroup>(); + + /// <summary> + /// A common object for all the work items int the STP + /// so we can mark them to cancel in O(1) + /// </summary> + private CanceledWorkItemsGroup _canceledSmartThreadPool = new CanceledWorkItemsGroup(); + + /// <summary> + /// Windows STP performance counters + /// </summary> + private ISTPInstancePerformanceCounters _windowsPCs = NullSTPInstancePerformanceCounters.Instance; + + /// <summary> + /// Local STP performance counters + /// </summary> + private ISTPInstancePerformanceCounters _localPCs = NullSTPInstancePerformanceCounters.Instance; + + +#if (WINDOWS_PHONE) + private static readonly Dictionary<int, ThreadEntry> _threadEntries = new Dictionary<int, ThreadEntry>(); +#elif (_WINDOWS_CE) + private static LocalDataStoreSlot _threadEntrySlot = Thread.AllocateDataSlot(); +#else + [ThreadStatic] + private static ThreadEntry _threadEntry; + +#endif + + /// <summary> + /// An event to call after a thread is created, but before + /// it's first use. + /// </summary> + private event ThreadInitializationHandler _onThreadInitialization; + + /// <summary> + /// An event to call when a thread is about to exit, after + /// it is no longer belong to the pool. + /// </summary> + private event ThreadTerminationHandler _onThreadTermination; + + #endregion + + #region Per thread properties + + /// <summary> + /// A reference to the current work item a thread from the thread pool + /// is executing. + /// </summary> + internal static ThreadEntry CurrentThreadEntry + { +#if (WINDOWS_PHONE) + get + { + lock(_threadEntries) + { + ThreadEntry threadEntry; + if (_threadEntries.TryGetValue(Thread.CurrentThread.ManagedThreadId, out threadEntry)) + { + return threadEntry; + } + } + return null; + } + set + { + lock(_threadEntries) + { + _threadEntries[Thread.CurrentThread.ManagedThreadId] = value; + } + } +#elif (_WINDOWS_CE) + get + { + //Thread.CurrentThread.ManagedThreadId + return Thread.GetData(_threadEntrySlot) as ThreadEntry; + } + set + { + Thread.SetData(_threadEntrySlot, value); + } +#else + get + { + return _threadEntry; + } + set + { + _threadEntry = value; + } +#endif + } + #endregion + + #region Construction and Finalization + + /// <summary> + /// Constructor + /// </summary> + public SmartThreadPool() + { + _stpStartInfo = new STPStartInfo(); + Initialize(); + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="idleTimeout">Idle timeout in milliseconds</param> + public SmartThreadPool(int idleTimeout) + { + _stpStartInfo = new STPStartInfo + { + IdleTimeout = idleTimeout, + }; + Initialize(); + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="startSuspended">Set it to True to start thread pool in suspended mode; Explicit call to Start() will be needed to start the Thread pool.</param> + public SmartThreadPool(bool startSuspended) + { + _stpStartInfo = new STPStartInfo + { + StartSuspended = startSuspended, + }; + Initialize(); + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="idleTimeout">Idle timeout in milliseconds</param> + /// <param name="maxWorkerThreads">Upper limit of threads in the pool</param> + public SmartThreadPool( + int idleTimeout, + int maxWorkerThreads) + { + _stpStartInfo = new STPStartInfo + { + IdleTimeout = idleTimeout, + MaxWorkerThreads = maxWorkerThreads, + }; + Initialize(); + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="idleTimeout">Idle timeout in milliseconds</param> + /// <param name="maxWorkerThreads">Upper limit of threads in the pool</param> + /// <param name="minWorkerThreads">Lower limit of threads in the pool</param> + public SmartThreadPool( + int idleTimeout, + int maxWorkerThreads, + int minWorkerThreads) + { + _stpStartInfo = new STPStartInfo + { + IdleTimeout = idleTimeout, + MaxWorkerThreads = maxWorkerThreads, + MinWorkerThreads = minWorkerThreads, + }; + Initialize(); + } + + /// <summary> + /// Constructor + /// </summary> + /// <param name="stpStartInfo">A SmartThreadPool configuration that overrides the default behavior</param> + public SmartThreadPool(STPStartInfo stpStartInfo) + { + _stpStartInfo = new STPStartInfo(stpStartInfo); + Initialize(); + } + + private void Initialize() + { + Name = _stpStartInfo.ThreadPoolName; + ValidateSTPStartInfo(); + + // _stpStartInfoRW stores a read/write copy of the STPStartInfo. + // Actually only MaxWorkerThreads and MinWorkerThreads are overwritten + + _isSuspended = _stpStartInfo.StartSuspended; + +#if (_WINDOWS_CE) || (_SILVERLIGHT) || (_MONO) || (WINDOWS_PHONE) + if (null != _stpStartInfo.PerformanceCounterInstanceName) + { + throw new NotSupportedException("Performance counters are not implemented for Compact Framework/Silverlight/Mono, instead use StpStartInfo.EnableLocalPerformanceCounters"); + } +#else + if (null != _stpStartInfo.PerformanceCounterInstanceName) + { + try + { + _windowsPCs = new STPInstancePerformanceCounters(_stpStartInfo.PerformanceCounterInstanceName); + } + catch (Exception e) + { + Debug.WriteLine("Unable to create Performance Counters: " + e); + _windowsPCs = NullSTPInstancePerformanceCounters.Instance; + } + } +#endif + + if (_stpStartInfo.EnableLocalPerformanceCounters) + { + _localPCs = new LocalSTPInstancePerformanceCounters(); + } + + // If the STP is not started suspended then start the threads. + if (!_isSuspended) + { + StartOptimalNumberOfThreads(); + } + } + + private void StartOptimalNumberOfThreads() + { + int threadsCount = Math.Max(_workItemsQueue.Count, _stpStartInfo.MinWorkerThreads); + threadsCount = Math.Min(threadsCount, _stpStartInfo.MaxWorkerThreads); + threadsCount -= _workerThreads.Count; + if (threadsCount > 0) + { + StartThreads(threadsCount); + } + } + + private void ValidateSTPStartInfo() + { + if (_stpStartInfo.MinWorkerThreads < 0) + { + throw new ArgumentOutOfRangeException( + "MinWorkerThreads", "MinWorkerThreads cannot be negative"); + } + + if (_stpStartInfo.MaxWorkerThreads <= 0) + { + throw new ArgumentOutOfRangeException( + "MaxWorkerThreads", "MaxWorkerThreads must be greater than zero"); + } + + if (_stpStartInfo.MinWorkerThreads > _stpStartInfo.MaxWorkerThreads) + { + throw new ArgumentOutOfRangeException( + "MinWorkerThreads, maxWorkerThreads", + "MaxWorkerThreads must be greater or equal to MinWorkerThreads"); + } + + if (_stpStartInfo.MaxQueueLength < 0) + { + throw new ArgumentOutOfRangeException( + "MaxQueueLength", + "MaxQueueLength must be >= 0 or null (for unbounded)"); + } + } + + private static void ValidateCallback(Delegate callback) + { + if(callback.GetInvocationList().Length > 1) + { + throw new NotSupportedException("SmartThreadPool doesn't support delegates chains"); + } + } + + #endregion + + #region Thread Processing + + /// <summary> + /// Waits on the queue for a work item, shutdown, or timeout. + /// </summary> + /// <returns> + /// Returns the WaitingCallback or null in case of timeout or shutdown. + /// </returns> + private WorkItem Dequeue() + { + WorkItem workItem = + _workItemsQueue.DequeueWorkItem(_stpStartInfo.IdleTimeout, _shuttingDownEvent); + + return workItem; + } + + /// <summary> + /// Put a new work item in the queue + /// </summary> + /// <param name="workItem">A work item to queue</param> + internal override void Enqueue(WorkItem workItem) + { + // Make sure the workItem is not null + Debug.Assert(null != workItem); + + IncrementWorkItemsCount(); + + workItem.CanceledSmartThreadPool = _canceledSmartThreadPool; + _workItemsQueue.EnqueueWorkItem(workItem); + workItem.WorkItemIsQueued(); + + // If all the threads are busy then try to create a new one + if (_currentWorkItemsCount > _workerThreads.Count) + { + StartThreads(1); + } + } + + private void IncrementWorkItemsCount() + { + _windowsPCs.SampleWorkItems(_workItemsQueue.Count, _workItemsProcessed); + _localPCs.SampleWorkItems(_workItemsQueue.Count, _workItemsProcessed); + + int count = Interlocked.Increment(ref _currentWorkItemsCount); + //Trace.WriteLine("WorkItemsCount = " + _currentWorkItemsCount.ToString()); + if (count == 1) + { + IsIdle = false; + _isIdleWaitHandle.Reset(); + } + } + + private void DecrementWorkItemsCount() + { + int count = Interlocked.Decrement(ref _currentWorkItemsCount); + //Trace.WriteLine("WorkItemsCount = " + _currentWorkItemsCount.ToString()); + if (count == 0) + { + IsIdle = true; + _isIdleWaitHandle.Set(); + } + + Interlocked.Increment(ref _workItemsProcessed); + + if (!_shutdown) + { + // The counter counts even if the work item was cancelled + _windowsPCs.SampleWorkItems(_workItemsQueue.Count, _workItemsProcessed); + _localPCs.SampleWorkItems(_workItemsQueue.Count, _workItemsProcessed); + } + + } + + internal void RegisterWorkItemsGroup(IWorkItemsGroup workItemsGroup) + { + _workItemsGroups[workItemsGroup] = workItemsGroup; + } + + internal void UnregisterWorkItemsGroup(IWorkItemsGroup workItemsGroup) + { + if (_workItemsGroups.Contains(workItemsGroup)) + { + _workItemsGroups.Remove(workItemsGroup); + } + } + + /// <summary> + /// Inform that the current thread is about to quit or quiting. + /// The same thread may call this method more than once. + /// </summary> + private void InformCompleted() + { + // There is no need to lock the two methods together + // since only the current thread removes itself + // and the _workerThreads is a synchronized dictionary + if (_workerThreads.Contains(Thread.CurrentThread)) + { + _workerThreads.Remove(Thread.CurrentThread); + _windowsPCs.SampleThreads(_workerThreads.Count, _inUseWorkerThreads); + _localPCs.SampleThreads(_workerThreads.Count, _inUseWorkerThreads); + } + } + + /// <summary> + /// Starts new threads + /// </summary> + /// <param name="threadsCount">The number of threads to start</param> + private void StartThreads(int threadsCount) + { + if (_isSuspended) + { + return; + } + + lock(_workerThreads.SyncRoot) + { + // Don't start threads on shut down + if (_shutdown) + { + return; + } + + for(int i = 0; i < threadsCount; ++i) + { + // Don't create more threads then the upper limit + if (_workerThreads.Count >= _stpStartInfo.MaxWorkerThreads) + { + return; + } + + // Create a new thread + +#if (_SILVERLIGHT) || (WINDOWS_PHONE) + Thread workerThread = new Thread(ProcessQueuedItems); +#else + Thread workerThread = + _stpStartInfo.MaxStackSize.HasValue + ? new Thread(ProcessQueuedItems, _stpStartInfo.MaxStackSize.Value) + : new Thread(ProcessQueuedItems); +#endif + // Configure the new thread and start it + workerThread.Name = "STP " + Name + " Thread #" + _threadCounter; + workerThread.IsBackground = _stpStartInfo.AreThreadsBackground; + +#if !(_SILVERLIGHT) && !(_WINDOWS_CE) && !(WINDOWS_PHONE) + if (_stpStartInfo.ApartmentState != ApartmentState.Unknown) + { + workerThread.SetApartmentState(_stpStartInfo.ApartmentState); + } +#endif + +#if !(_SILVERLIGHT) && !(WINDOWS_PHONE) + workerThread.Priority = _stpStartInfo.ThreadPriority; +#endif + workerThread.Start(); + ++_threadCounter; + + // Add it to the dictionary and update its creation time. + _workerThreads[workerThread] = new ThreadEntry(this); + + _windowsPCs.SampleThreads(_workerThreads.Count, _inUseWorkerThreads); + _localPCs.SampleThreads(_workerThreads.Count, _inUseWorkerThreads); + } + } + } + + /// <summary> + /// A worker thread method that processes work items from the work items queue. + /// </summary> + private void ProcessQueuedItems() + { + // Keep the entry of the dictionary as thread's variable to avoid the synchronization locks + // of the dictionary. + CurrentThreadEntry = _workerThreads[Thread.CurrentThread]; + + FireOnThreadInitialization(); + + try + { + bool bInUseWorkerThreadsWasIncremented = false; + + // Process until shutdown. + while(!_shutdown) + { + // Update the last time this thread was seen alive. + // It's good for debugging. + CurrentThreadEntry.IAmAlive(); + + // The following block handles the when the MaxWorkerThreads has been + // incremented by the user at run-time. + // Double lock for quit. + if (_workerThreads.Count > _stpStartInfo.MaxWorkerThreads) + { + lock (_workerThreads.SyncRoot) + { + if (_workerThreads.Count > _stpStartInfo.MaxWorkerThreads) + { + // Inform that the thread is quiting and then quit. + // This method must be called within this lock or else + // more threads will quit and the thread pool will go + // below the lower limit. + InformCompleted(); + break; + } + } + } + + // Wait for a work item, shutdown, or timeout + WorkItem workItem = Dequeue(); + + // Update the last time this thread was seen alive. + // It's good for debugging. + CurrentThreadEntry.IAmAlive(); + + // On timeout or shut down. + if (null == workItem) + { + // Double lock for quit. + if (_workerThreads.Count > _stpStartInfo.MinWorkerThreads) + { + lock(_workerThreads.SyncRoot) + { + if (_workerThreads.Count > _stpStartInfo.MinWorkerThreads) + { + // Inform that the thread is quiting and then quit. + // This method must be called within this lock or else + // more threads will quit and the thread pool will go + // below the lower limit. + InformCompleted(); + break; + } + } + } + } + + // If we didn't quit then skip to the next iteration. + if (null == workItem) + { + continue; + } + + try + { + // Initialize the value to false + bInUseWorkerThreadsWasIncremented = false; + + // Set the Current Work Item of the thread. + // Store the Current Work Item before the workItem.StartingWorkItem() is called, + // so WorkItem.Cancel can work when the work item is between InQueue and InProgress + // states. + // If the work item has been cancelled BEFORE the workItem.StartingWorkItem() + // (work item is in InQueue state) then workItem.StartingWorkItem() will return false. + // If the work item has been cancelled AFTER the workItem.StartingWorkItem() then + // (work item is in InProgress state) then the thread will be aborted + CurrentThreadEntry.CurrentWorkItem = workItem; + + // Change the state of the work item to 'in progress' if possible. + // We do it here so if the work item has been canceled we won't + // increment the _inUseWorkerThreads. + // The cancel mechanism doesn't delete items from the queue, + // it marks the work item as canceled, and when the work item + // is dequeued, we just skip it. + // If the post execute of work item is set to always or to + // call when the work item is canceled then the StartingWorkItem() + // will return true, so the post execute can run. + if (!workItem.StartingWorkItem()) + { + continue; + } + + // Execute the callback. Make sure to accurately + // record how many callbacks are currently executing. + int inUseWorkerThreads = Interlocked.Increment(ref _inUseWorkerThreads); + _windowsPCs.SampleThreads(_workerThreads.Count, inUseWorkerThreads); + _localPCs.SampleThreads(_workerThreads.Count, inUseWorkerThreads); + + // Mark that the _inUseWorkerThreads incremented, so in the finally{} + // statement we will decrement it correctly. + bInUseWorkerThreadsWasIncremented = true; + + workItem.FireWorkItemStarted(); + + ExecuteWorkItem(workItem); + } + catch(Exception ex) + { + ex.GetHashCode(); + // Do nothing + } + finally + { + workItem.DisposeOfState(); + + // Set the CurrentWorkItem to null, since we + // no longer run user's code. + CurrentThreadEntry.CurrentWorkItem = null; + + // Decrement the _inUseWorkerThreads only if we had + // incremented it. Note the cancelled work items don't + // increment _inUseWorkerThreads. + if (bInUseWorkerThreadsWasIncremented) + { + int inUseWorkerThreads = Interlocked.Decrement(ref _inUseWorkerThreads); + _windowsPCs.SampleThreads(_workerThreads.Count, inUseWorkerThreads); + _localPCs.SampleThreads(_workerThreads.Count, inUseWorkerThreads); + } + + // Notify that the work item has been completed. + // WorkItemsGroup may enqueue their next work item. + workItem.FireWorkItemCompleted(); + + // Decrement the number of work items here so the idle + // ManualResetEvent won't fluctuate. + DecrementWorkItemsCount(); + } + } + } + catch(ThreadAbortException tae) + { + tae.GetHashCode(); + // Handle the abort exception gracfully. +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + Thread.ResetAbort(); +#endif + } + catch(Exception e) + { + Debug.Assert(null != e); + } + finally + { + InformCompleted(); + FireOnThreadTermination(); + } + } + + private void ExecuteWorkItem(WorkItem workItem) + { + _windowsPCs.SampleWorkItemsWaitTime(workItem.WaitingTime); + _localPCs.SampleWorkItemsWaitTime(workItem.WaitingTime); + try + { + workItem.Execute(); + } + finally + { + _windowsPCs.SampleWorkItemsProcessTime(workItem.ProcessTime); + _localPCs.SampleWorkItemsProcessTime(workItem.ProcessTime); + } + } + + + #endregion + + #region Public Methods + + private void ValidateWaitForIdle() + { + if (null != CurrentThreadEntry && CurrentThreadEntry.AssociatedSmartThreadPool == this) + { + throw new NotSupportedException( + "WaitForIdle cannot be called from a thread on its SmartThreadPool, it causes a deadlock"); + } + } + + internal static void ValidateWorkItemsGroupWaitForIdle(IWorkItemsGroup workItemsGroup) + { + if (null == CurrentThreadEntry) + { + return; + } + + WorkItem workItem = CurrentThreadEntry.CurrentWorkItem; + ValidateWorkItemsGroupWaitForIdleImpl(workItemsGroup, workItem); + if ((null != workItemsGroup) && + (null != workItem) && + CurrentThreadEntry.CurrentWorkItem.WasQueuedBy(workItemsGroup)) + { + throw new NotSupportedException("WaitForIdle cannot be called from a thread on its SmartThreadPool, it causes a deadlock"); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ValidateWorkItemsGroupWaitForIdleImpl(IWorkItemsGroup workItemsGroup, WorkItem workItem) + { + if ((null != workItemsGroup) && + (null != workItem) && + workItem.WasQueuedBy(workItemsGroup)) + { + throw new NotSupportedException("WaitForIdle cannot be called from a thread on its SmartThreadPool, it causes a deadlock"); + } + } + + /// <summary> + /// Force the SmartThreadPool to shutdown + /// </summary> + public void Shutdown() + { + Shutdown(true, 0); + } + + /// <summary> + /// Force the SmartThreadPool to shutdown with timeout + /// </summary> + public void Shutdown(bool forceAbort, TimeSpan timeout) + { + Shutdown(forceAbort, (int)timeout.TotalMilliseconds); + } + + /// <summary> + /// Empties the queue of work items and abort the threads in the pool. + /// </summary> + public void Shutdown(bool forceAbort, int millisecondsTimeout) + { + ValidateNotDisposed(); + + ISTPInstancePerformanceCounters pcs = _windowsPCs; + + if (NullSTPInstancePerformanceCounters.Instance != _windowsPCs) + { + // Set the _pcs to "null" to stop updating the performance + // counters + _windowsPCs = NullSTPInstancePerformanceCounters.Instance; + + pcs.Dispose(); + } + + Thread [] threads; + lock(_workerThreads.SyncRoot) + { + // Shutdown the work items queue + _workItemsQueue.Dispose(); + + // Signal the threads to exit + _shutdown = true; + _shuttingDownEvent.Set(); + + // Make a copy of the threads' references in the pool + threads = new Thread [_workerThreads.Count]; + _workerThreads.Keys.CopyTo(threads, 0); + } + + int millisecondsLeft = millisecondsTimeout; + System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); + //DateTime start = DateTime.UtcNow; + bool waitInfinitely = (Timeout.Infinite == millisecondsTimeout); + bool timeout = false; + + // Each iteration we update the time left for the timeout. + foreach(Thread thread in threads) + { + // Join don't work with negative numbers + if (!waitInfinitely && (millisecondsLeft < 0)) + { + timeout = true; + break; + } + + // Wait for the thread to terminate + bool success = thread.Join(millisecondsLeft); + if(!success) + { + timeout = true; + break; + } + + if(!waitInfinitely) + { + // Update the time left to wait + //TimeSpan ts = DateTime.UtcNow - start; + millisecondsLeft = millisecondsTimeout - (int)stopwatch.ElapsedMilliseconds; + } + } + + if (timeout && forceAbort) + { + // Abort the threads in the pool + foreach(Thread thread in threads) + { + + if ((thread != null) +#if !(_WINDOWS_CE) + && thread.IsAlive +#endif + ) + { + try + { + thread.Abort(); // Shutdown + } + catch(SecurityException e) + { + e.GetHashCode(); + } + catch(ThreadStateException ex) + { + ex.GetHashCode(); + // In case the thread has been terminated + // after the check if it is alive. + } + } + } + } + } + + /// <summary> + /// Wait for all work items to complete + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <returns> + /// true when every work item in workItemResults has completed; otherwise false. + /// </returns> + public static bool WaitAll( + IWaitableResult [] waitableResults) + { + return WaitAll(waitableResults, Timeout.Infinite, true); + } + + /// <summary> + /// Wait for all work items to complete + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="timeout">The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. </param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <returns> + /// true when every work item in workItemResults has completed; otherwise false. + /// </returns> + public static bool WaitAll( + IWaitableResult [] waitableResults, + TimeSpan timeout, + bool exitContext) + { + return WaitAll(waitableResults, (int)timeout.TotalMilliseconds, exitContext); + } + + /// <summary> + /// Wait for all work items to complete + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="timeout">The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. </param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param> + /// <returns> + /// true when every work item in workItemResults has completed; otherwise false. + /// </returns> + public static bool WaitAll( + IWaitableResult[] waitableResults, + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WaitAll(waitableResults, (int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle); + } + + /// <summary> + /// Wait for all work items to complete + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <returns> + /// true when every work item in workItemResults has completed; otherwise false. + /// </returns> + public static bool WaitAll( + IWaitableResult [] waitableResults, + int millisecondsTimeout, + bool exitContext) + { + return WorkItem.WaitAll(waitableResults, millisecondsTimeout, exitContext, null); + } + + /// <summary> + /// Wait for all work items to complete + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param> + /// <returns> + /// true when every work item in workItemResults has completed; otherwise false. + /// </returns> + public static bool WaitAll( + IWaitableResult[] waitableResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WorkItem.WaitAll(waitableResults, millisecondsTimeout, exitContext, cancelWaitHandle); + } + + + /// <summary> + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <returns> + /// The array index of the work item result that satisfied the wait, or WaitTimeout if any of the work items has been canceled. + /// </returns> + public static int WaitAny( + IWaitableResult [] waitableResults) + { + return WaitAny(waitableResults, Timeout.Infinite, true); + } + + /// <summary> + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="timeout">The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. </param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <returns> + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// </returns> + public static int WaitAny( + IWaitableResult[] waitableResults, + TimeSpan timeout, + bool exitContext) + { + return WaitAny(waitableResults, (int)timeout.TotalMilliseconds, exitContext); + } + + /// <summary> + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="timeout">The number of milliseconds to wait, or a TimeSpan that represents -1 milliseconds to wait indefinitely. </param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param> + /// <returns> + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// </returns> + public static int WaitAny( + IWaitableResult [] waitableResults, + TimeSpan timeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WaitAny(waitableResults, (int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle); + } + + /// <summary> + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <returns> + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// </returns> + public static int WaitAny( + IWaitableResult [] waitableResults, + int millisecondsTimeout, + bool exitContext) + { + return WorkItem.WaitAny(waitableResults, millisecondsTimeout, exitContext, null); + } + + /// <summary> + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param> + /// <returns> + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// </returns> + public static int WaitAny( + IWaitableResult [] waitableResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + return WorkItem.WaitAny(waitableResults, millisecondsTimeout, exitContext, cancelWaitHandle); + } + + /// <summary> + /// Creates a new WorkItemsGroup. + /// </summary> + /// <param name="concurrency">The number of work items that can be run concurrently</param> + /// <returns>A reference to the WorkItemsGroup</returns> + public IWorkItemsGroup CreateWorkItemsGroup(int concurrency) + { + IWorkItemsGroup workItemsGroup = new WorkItemsGroup(this, concurrency, _stpStartInfo); + return workItemsGroup; + } + + /// <summary> + /// Creates a new WorkItemsGroup. + /// </summary> + /// <param name="concurrency">The number of work items that can be run concurrently</param> + /// <param name="wigStartInfo">A WorkItemsGroup configuration that overrides the default behavior</param> + /// <returns>A reference to the WorkItemsGroup</returns> + public IWorkItemsGroup CreateWorkItemsGroup(int concurrency, WIGStartInfo wigStartInfo) + { + IWorkItemsGroup workItemsGroup = new WorkItemsGroup(this, concurrency, wigStartInfo); + return workItemsGroup; + } + + #region Fire Thread's Events + + private void FireOnThreadInitialization() + { + if (null != _onThreadInitialization) + { + foreach (ThreadInitializationHandler tih in _onThreadInitialization.GetInvocationList()) + { + try + { + tih(); + } + catch (Exception e) + { + e.GetHashCode(); + Debug.Assert(false); + throw; + } + } + } + } + + private void FireOnThreadTermination() + { + if (null != _onThreadTermination) + { + foreach (ThreadTerminationHandler tth in _onThreadTermination.GetInvocationList()) + { + try + { + tth(); + } + catch (Exception e) + { + e.GetHashCode(); + Debug.Assert(false); + throw; + } + } + } + } + + #endregion + + /// <summary> + /// This event is fired when a thread is created. + /// Use it to initialize a thread before the work items use it. + /// </summary> + public event ThreadInitializationHandler OnThreadInitialization + { + add { _onThreadInitialization += value; } + remove { _onThreadInitialization -= value; } + } + + /// <summary> + /// This event is fired when a thread is terminating. + /// Use it for cleanup. + /// </summary> + public event ThreadTerminationHandler OnThreadTermination + { + add { _onThreadTermination += value; } + remove { _onThreadTermination -= value; } + } + + + internal void CancelAbortWorkItemsGroup(WorkItemsGroup wig) + { + foreach (ThreadEntry threadEntry in _workerThreads.Values) + { + WorkItem workItem = threadEntry.CurrentWorkItem; + if (null != workItem && + workItem.WasQueuedBy(wig) && + !workItem.IsCanceled) + { + threadEntry.CurrentWorkItem.GetWorkItemResult().Cancel(true); + } + } + } + + private void ValidateQueueIsWithinLimits() + { + // Keep a local copy; if a client changes the length while this is executing, + // we'll want to use the same value throughout. + var maxQueueLength = _stpStartInfo.MaxQueueLength; + + if (maxQueueLength == null) + { + return; + } + + // Instead of just looking at the current queue length here, account for the + // fact that the pool is going to scale up its threads if it's not yet at its + // maximum and there are queued items. This means that the queue length limit + // may be briefly exceeded while the pool is scaling up. + if (_currentWorkItemsCount >= maxQueueLength + MaxThreads) + { + throw new QueueRejectedException("Queue is at its maximum (" + maxQueueLength + ")"); + } + } + + #endregion + + #region Properties + + /// <summary> + /// Get/Set the lower limit of threads in the pool. + /// </summary> + public int MinThreads + { + get + { + ValidateNotDisposed(); + return _stpStartInfo.MinWorkerThreads; + } + set + { + Debug.Assert(value >= 0); + Debug.Assert(value <= _stpStartInfo.MaxWorkerThreads); + if (_stpStartInfo.MaxWorkerThreads < value) + { + _stpStartInfo.MaxWorkerThreads = value; + } + _stpStartInfo.MinWorkerThreads = value; + StartOptimalNumberOfThreads(); + } + } + + /// <summary> + /// Get/Set the upper limit of threads in the pool. + /// </summary> + public int MaxThreads + { + get + { + ValidateNotDisposed(); + return _stpStartInfo.MaxWorkerThreads; + } + + set + { + Debug.Assert(value > 0); + Debug.Assert(value >= _stpStartInfo.MinWorkerThreads); + if (_stpStartInfo.MinWorkerThreads > value) + { + _stpStartInfo.MinWorkerThreads = value; + } + _stpStartInfo.MaxWorkerThreads = value; + StartOptimalNumberOfThreads(); + } + } + + public int? MaxQueueLength + { + get + { + ValidateNotDisposed(); + return _stpStartInfo.MaxQueueLength; + } + + set + { + _stpStartInfo.MaxQueueLength = value; + } + } + + /// <summary> + /// Get the number of threads in the thread pool. + /// Should be between the lower and the upper limits. + /// </summary> + public int ActiveThreads + { + get + { + ValidateNotDisposed(); + return _workerThreads.Count; + } + } + + /// <summary> + /// Get the number of work items that haven't finished execution (i.e. + /// items being worked on by threads + items in the queue). + /// </summary> + public int CurrentWorkItemsCount + { + get + { + ValidateNotDisposed(); + return _currentWorkItemsCount; + } + } + + /// <summary> + /// Returns true if the current running work item has been cancelled. + /// Must be used within the work item's callback method. + /// The work item should sample this value in order to know if it + /// needs to quit before its completion. + /// </summary> + public static bool IsWorkItemCanceled + { + get + { + return CurrentThreadEntry.CurrentWorkItem.IsCanceled; + } + } + + /// <summary> + /// Checks if the work item has been cancelled, and if yes then abort the thread. + /// Can be used with Cancel and timeout + /// </summary> + public static void AbortOnWorkItemCancel() + { + if (IsWorkItemCanceled) + { + Thread.CurrentThread.Abort(); + } + } + + /// <summary> + /// Thread Pool start information (readonly) + /// </summary> + public STPStartInfo STPStartInfo + { + get + { + return _stpStartInfo.AsReadOnly(); + } + } + + public bool IsShuttingdown + { + get { return _shutdown; } + } + + /// <summary> + /// Return the local calculated performance counters + /// Available only if STPStartInfo.EnableLocalPerformanceCounters is true. + /// </summary> + public ISTPPerformanceCountersReader PerformanceCountersReader + { + get { return (ISTPPerformanceCountersReader)_localPCs; } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + if (!_isDisposed) + { + if (!_shutdown) + { + Shutdown(); + } + + if (null != _shuttingDownEvent) + { + _shuttingDownEvent.Close(); + _shuttingDownEvent = null; + } + _workerThreads.Clear(); + + if (null != _isIdleWaitHandle) + { + _isIdleWaitHandle.Close(); + _isIdleWaitHandle = null; + } + + _isDisposed = true; + } + } + + private void ValidateNotDisposed() + { + if(_isDisposed) + { + throw new ObjectDisposedException(GetType().ToString(), "The SmartThreadPool has been shutdown"); + } + } + #endregion + + #region WorkItemsGroupBase Overrides + + /// <summary> + /// Get/Set the maximum number of work items that execute cocurrency on the thread pool + /// </summary> + public override int Concurrency + { + get { return MaxThreads; } + set { MaxThreads = value; } + } + + /// <summary> + /// Get the number of busy (not idle) threads in the thread pool. + /// </summary> + public override int InUseThreads + { + get + { + ValidateNotDisposed(); + return _inUseWorkerThreads; + } + } + + /// <summary> + /// Get the number of work items in the queue. + /// </summary> + public override int WaitingCallbacks + { + get + { + ValidateNotDisposed(); + return _workItemsQueue.Count; + } + } + + /// <summary> + /// Get an array with all the state objects of the currently running items. + /// The array represents a snap shot and impact performance. + /// </summary> + public override object[] GetStates() + { + object[] states = _workItemsQueue.GetStates(); + return states; + } + + /// <summary> + /// WorkItemsGroup start information (readonly) + /// </summary> + public override WIGStartInfo WIGStartInfo + { + get { return _stpStartInfo.AsReadOnly(); } + } + + /// <summary> + /// Start the thread pool if it was started suspended. + /// If it is already running, this method is ignored. + /// </summary> + public override void Start() + { + if (!_isSuspended) + { + return; + } + _isSuspended = false; + + ICollection workItemsGroups = _workItemsGroups.Values; + foreach (WorkItemsGroup workItemsGroup in workItemsGroups) + { + workItemsGroup.OnSTPIsStarting(); + } + + StartOptimalNumberOfThreads(); + } + + /// <summary> + /// Cancel all work items using thread abortion + /// </summary> + /// <param name="abortExecution">True to stop work items by raising ThreadAbortException</param> + public override void Cancel(bool abortExecution) + { + _canceledSmartThreadPool.IsCanceled = true; + _canceledSmartThreadPool = new CanceledWorkItemsGroup(); + + ICollection workItemsGroups = _workItemsGroups.Values; + foreach (WorkItemsGroup workItemsGroup in workItemsGroups) + { + workItemsGroup.Cancel(abortExecution); + } + + if (abortExecution) + { + foreach (ThreadEntry threadEntry in _workerThreads.Values) + { + WorkItem workItem = threadEntry.CurrentWorkItem; + if (null != workItem && + threadEntry.AssociatedSmartThreadPool == this && + !workItem.IsCanceled) + { + threadEntry.CurrentWorkItem.GetWorkItemResult().Cancel(true); + } + } + } + } + + /// <summary> + /// Wait for the thread pool to be idle + /// </summary> + public override bool WaitForIdle(int millisecondsTimeout) + { + ValidateWaitForIdle(); + return STPEventWaitHandle.WaitOne(_isIdleWaitHandle, millisecondsTimeout, false); + } + + /// <summary> + /// This event is fired when all work items are completed. + /// (When IsIdle changes to true) + /// This event only work on WorkItemsGroup. On SmartThreadPool + /// it throws the NotImplementedException. + /// </summary> + public override event WorkItemsGroupIdleHandler OnIdle + { + add + { + throw new NotImplementedException("This event is not implemented in the SmartThreadPool class. Please create a WorkItemsGroup in order to use this feature."); + //_onIdle += value; + } + remove + { + throw new NotImplementedException("This event is not implemented in the SmartThreadPool class. Please create a WorkItemsGroup in order to use this feature."); + //_onIdle -= value; + } + } + + internal override void PreQueueWorkItem() + { + ValidateNotDisposed(); + + // This gives no preference to items of higher priority. + ValidateQueueIsWithinLimits(); + } + + #endregion + + #region Join, Choice, Pipe, etc. + + /// <summary> + /// Executes all actions in parallel. + /// Returns when they all finish. + /// </summary> + /// <param name="actions">Actions to execute</param> + public void Join(IEnumerable<Action> actions) + { + WIGStartInfo wigStartInfo = new WIGStartInfo { StartSuspended = true }; + IWorkItemsGroup workItemsGroup = CreateWorkItemsGroup(int.MaxValue, wigStartInfo); + foreach (Action action in actions) + { + workItemsGroup.QueueWorkItem(action); + } + workItemsGroup.Start(); + workItemsGroup.WaitForIdle(); + } + + /// <summary> + /// Executes all actions in parallel. + /// Returns when they all finish. + /// </summary> + /// <param name="actions">Actions to execute</param> + public void Join(params Action[] actions) + { + Join((IEnumerable<Action>)actions); + } + + private class ChoiceIndex + { + public int _index = -1; + } + + /// <summary> + /// Executes all actions in parallel + /// Returns when the first one completes + /// </summary> + /// <param name="actions">Actions to execute</param> + public int Choice(IEnumerable<Action> actions) + { + WIGStartInfo wigStartInfo = new WIGStartInfo { StartSuspended = true }; + IWorkItemsGroup workItemsGroup = CreateWorkItemsGroup(int.MaxValue, wigStartInfo); + + ManualResetEvent anActionCompleted = new ManualResetEvent(false); + + ChoiceIndex choiceIndex = new ChoiceIndex(); + + int i = 0; + foreach (Action action in actions) + { + Action act = action; + int value = i; + workItemsGroup.QueueWorkItem(() => { act(); Interlocked.CompareExchange(ref choiceIndex._index, value, -1); anActionCompleted.Set(); }); + ++i; + } + workItemsGroup.Start(); + anActionCompleted.WaitOne(); + + return choiceIndex._index; + } + + /// <summary> + /// Executes all actions in parallel + /// Returns when the first one completes + /// </summary> + /// <param name="actions">Actions to execute</param> + public int Choice(params Action[] actions) + { + return Choice((IEnumerable<Action>)actions); + } + + /// <summary> + /// Executes actions in sequence asynchronously. + /// Returns immediately. + /// </summary> + /// <param name="pipeState">A state context that passes </param> + /// <param name="actions">Actions to execute in the order they should run</param> + public void Pipe<T>(T pipeState, IEnumerable<Action<T>> actions) + { + WIGStartInfo wigStartInfo = new WIGStartInfo { StartSuspended = true }; + IWorkItemsGroup workItemsGroup = CreateWorkItemsGroup(1, wigStartInfo); + foreach (Action<T> action in actions) + { + Action<T> act = action; + workItemsGroup.QueueWorkItem(() => act(pipeState)); + } + workItemsGroup.Start(); + workItemsGroup.WaitForIdle(); + } + + /// <summary> + /// Executes actions in sequence asynchronously. + /// Returns immediately. + /// </summary> + /// <param name="pipeState"></param> + /// <param name="actions">Actions to execute in the order they should run</param> + public void Pipe<T>(T pipeState, params Action<T>[] actions) + { + Pipe(pipeState, (IEnumerable<Action<T>>)actions); + } + #endregion + } + #endregion +} diff --git a/Networking/SmartThreadPool/Stopwatch.cs b/Networking/SmartThreadPool/Stopwatch.cs new file mode 100644 index 0000000..c6f88d9 --- /dev/null +++ b/Networking/SmartThreadPool/Stopwatch.cs @@ -0,0 +1,108 @@ +using System; + +namespace Amib.Threading.Internal +{ + /// <summary> + /// Stopwatch class + /// Used with WindowsCE and Silverlight which don't have Stopwatch + /// </summary> + internal class Stopwatch + { + private long _elapsed; + private bool _isRunning; + private long _startTimeStamp; + + public Stopwatch() + { + Reset(); + } + + private long GetElapsedDateTimeTicks() + { + long rawElapsedTicks = GetRawElapsedTicks(); + return rawElapsedTicks; + } + + private long GetRawElapsedTicks() + { + long elapsed = _elapsed; + if (_isRunning) + { + long ticks = GetTimestamp() - _startTimeStamp; + elapsed += ticks; + } + return elapsed; + } + + public static long GetTimestamp() + { + return DateTime.UtcNow.Ticks; + } + + public void Reset() + { + _elapsed = 0L; + _isRunning = false; + _startTimeStamp = 0L; + } + + public void Start() + { + if (!_isRunning) + { + _startTimeStamp = GetTimestamp(); + _isRunning = true; + } + } + + public static Stopwatch StartNew() + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + return stopwatch; + } + + public void Stop() + { + if (_isRunning) + { + long ticks = GetTimestamp() - _startTimeStamp; + _elapsed += ticks; + _isRunning = false; + } + } + + // Properties + public TimeSpan Elapsed + { + get + { + return new TimeSpan(GetElapsedDateTimeTicks()); + } + } + + public long ElapsedMilliseconds + { + get + { + return (GetElapsedDateTimeTicks() / 0x2710L); + } + } + + public long ElapsedTicks + { + get + { + return GetRawElapsedTicks(); + } + } + + public bool IsRunning + { + get + { + return _isRunning; + } + } + } +} diff --git a/Networking/SmartThreadPool/SynchronizedDictionary.cs b/Networking/SmartThreadPool/SynchronizedDictionary.cs new file mode 100644 index 0000000..0cce19f --- /dev/null +++ b/Networking/SmartThreadPool/SynchronizedDictionary.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; + +namespace Amib.Threading.Internal +{ + internal class SynchronizedDictionary<TKey, TValue> + { + private readonly Dictionary<TKey, TValue> _dictionary; + private readonly object _lock; + + public SynchronizedDictionary() + { + _lock = new object(); + _dictionary = new Dictionary<TKey, TValue>(); + } + + public int Count + { + get { return _dictionary.Count; } + } + + public bool Contains(TKey key) + { + lock (_lock) + { + return _dictionary.ContainsKey(key); + } + } + + public void Remove(TKey key) + { + lock (_lock) + { + _dictionary.Remove(key); + } + } + + public object SyncRoot + { + get { return _lock; } + } + + public TValue this[TKey key] + { + get + { + lock (_lock) + { + return _dictionary[key]; + } + } + set + { + lock (_lock) + { + _dictionary[key] = value; + } + } + } + + public Dictionary<TKey, TValue>.KeyCollection Keys + { + get + { + lock (_lock) + { + return _dictionary.Keys; + } + } + } + + public Dictionary<TKey, TValue>.ValueCollection Values + { + get + { + lock (_lock) + { + return _dictionary.Values; + } + } + } + public void Clear() + { + lock (_lock) + { + _dictionary.Clear(); + } + } + } +} diff --git a/Networking/SmartThreadPool/WIGStartInfo.cs b/Networking/SmartThreadPool/WIGStartInfo.cs new file mode 100644 index 0000000..8af195b --- /dev/null +++ b/Networking/SmartThreadPool/WIGStartInfo.cs @@ -0,0 +1,171 @@ +using System; + +namespace Amib.Threading +{ + /// <summary> + /// Summary description for WIGStartInfo. + /// </summary> + public class WIGStartInfo + { + private bool _useCallerCallContext; + private bool _useCallerHttpContext; + private bool _disposeOfStateObjects; + private CallToPostExecute _callToPostExecute; + private PostExecuteWorkItemCallback _postExecuteWorkItemCallback; + private bool _startSuspended; + private WorkItemPriority _workItemPriority; + private bool _fillStateWithArgs; + + protected bool _readOnly; + + public WIGStartInfo() + { + _fillStateWithArgs = SmartThreadPool.DefaultFillStateWithArgs; + _workItemPriority = SmartThreadPool.DefaultWorkItemPriority; + _startSuspended = SmartThreadPool.DefaultStartSuspended; + _postExecuteWorkItemCallback = SmartThreadPool.DefaultPostExecuteWorkItemCallback; + _callToPostExecute = SmartThreadPool.DefaultCallToPostExecute; + _disposeOfStateObjects = SmartThreadPool.DefaultDisposeOfStateObjects; + _useCallerHttpContext = SmartThreadPool.DefaultUseCallerHttpContext; + _useCallerCallContext = SmartThreadPool.DefaultUseCallerCallContext; + } + + public WIGStartInfo(WIGStartInfo wigStartInfo) + { + _useCallerCallContext = wigStartInfo.UseCallerCallContext; + _useCallerHttpContext = wigStartInfo.UseCallerHttpContext; + _disposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + _callToPostExecute = wigStartInfo.CallToPostExecute; + _postExecuteWorkItemCallback = wigStartInfo.PostExecuteWorkItemCallback; + _workItemPriority = wigStartInfo.WorkItemPriority; + _startSuspended = wigStartInfo.StartSuspended; + _fillStateWithArgs = wigStartInfo.FillStateWithArgs; + } + + protected void ThrowIfReadOnly() + { + if (_readOnly) + { + throw new NotSupportedException("This is a readonly instance and set is not supported"); + } + } + + /// <summary> + /// Get/Set if to use the caller's security context + /// </summary> + public virtual bool UseCallerCallContext + { + get { return _useCallerCallContext; } + set + { + ThrowIfReadOnly(); + _useCallerCallContext = value; + } + } + + + /// <summary> + /// Get/Set if to use the caller's HTTP context + /// </summary> + public virtual bool UseCallerHttpContext + { + get { return _useCallerHttpContext; } + set + { + ThrowIfReadOnly(); + _useCallerHttpContext = value; + } + } + + + /// <summary> + /// Get/Set if to dispose of the state object of a work item + /// </summary> + public virtual bool DisposeOfStateObjects + { + get { return _disposeOfStateObjects; } + set + { + ThrowIfReadOnly(); + _disposeOfStateObjects = value; + } + } + + + /// <summary> + /// Get/Set the run the post execute options + /// </summary> + public virtual CallToPostExecute CallToPostExecute + { + get { return _callToPostExecute; } + set + { + ThrowIfReadOnly(); + _callToPostExecute = value; + } + } + + + /// <summary> + /// Get/Set the default post execute callback + /// </summary> + public virtual PostExecuteWorkItemCallback PostExecuteWorkItemCallback + { + get { return _postExecuteWorkItemCallback; } + set + { + ThrowIfReadOnly(); + _postExecuteWorkItemCallback = value; + } + } + + + /// <summary> + /// Get/Set if the work items execution should be suspended until the Start() + /// method is called. + /// </summary> + public virtual bool StartSuspended + { + get { return _startSuspended; } + set + { + ThrowIfReadOnly(); + _startSuspended = value; + } + } + + + /// <summary> + /// Get/Set the default priority that a work item gets when it is enqueued + /// </summary> + public virtual WorkItemPriority WorkItemPriority + { + get { return _workItemPriority; } + set { _workItemPriority = value; } + } + + /// <summary> + /// Get/Set the if QueueWorkItem of Action<...>/Func<...> fill the + /// arguments as an object array into the state of the work item. + /// The arguments can be access later by IWorkItemResult.State. + /// </summary> + public virtual bool FillStateWithArgs + { + get { return _fillStateWithArgs; } + set + { + ThrowIfReadOnly(); + _fillStateWithArgs = value; + } + } + + /// <summary> + /// Get a readonly version of this WIGStartInfo + /// </summary> + /// <returns>Returns a readonly reference to this WIGStartInfoRO</returns> + public WIGStartInfo AsReadOnly() + { + return new WIGStartInfo(this) { _readOnly = true }; + } + } +} diff --git a/Networking/SmartThreadPool/WorkItem.WorkItemResult.cs b/Networking/SmartThreadPool/WorkItem.WorkItemResult.cs new file mode 100644 index 0000000..f965c92 --- /dev/null +++ b/Networking/SmartThreadPool/WorkItem.WorkItemResult.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Amib.Threading.Internal +{ + public partial class WorkItem + { + #region WorkItemResult class + + private class WorkItemResult : IWorkItemResult, IInternalWorkItemResult, IInternalWaitableResult + { + /// <summary> + /// A back reference to the work item + /// </summary> + private readonly WorkItem _workItem; + + public WorkItemResult(WorkItem workItem) + { + _workItem = workItem; + } + + internal WorkItem GetWorkItem() + { + return _workItem; + } + + #region IWorkItemResult Members + + public bool IsCompleted + { + get + { + return _workItem.IsCompleted; + } + } + + public bool IsCanceled + { + get + { + return _workItem.IsCanceled; + } + } + + public object GetResult() + { + return _workItem.GetResult(Timeout.Infinite, true, null); + } + + public object GetResult(int millisecondsTimeout, bool exitContext) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, null); + } + + public object GetResult(TimeSpan timeout, bool exitContext) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, null); + } + + public object GetResult(int millisecondsTimeout, bool exitContext, WaitHandle cancelWaitHandle) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, cancelWaitHandle); + } + + public object GetResult(TimeSpan timeout, bool exitContext, WaitHandle cancelWaitHandle) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle); + } + + public object GetResult(out Exception e) + { + return _workItem.GetResult(Timeout.Infinite, true, null, out e); + } + + public object GetResult(int millisecondsTimeout, bool exitContext, out Exception e) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, null, out e); + } + + public object GetResult(TimeSpan timeout, bool exitContext, out Exception e) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, null, out e); + } + + public object GetResult(int millisecondsTimeout, bool exitContext, WaitHandle cancelWaitHandle, out Exception e) + { + return _workItem.GetResult(millisecondsTimeout, exitContext, cancelWaitHandle, out e); + } + + public object GetResult(TimeSpan timeout, bool exitContext, WaitHandle cancelWaitHandle, out Exception e) + { + return _workItem.GetResult((int)timeout.TotalMilliseconds, exitContext, cancelWaitHandle, out e); + } + + public bool Cancel() + { + return Cancel(false); + } + + public bool Cancel(bool abortExecution) + { + return _workItem.Cancel(abortExecution); + } + + public object State + { + get + { + return _workItem._state; + } + } + + public WorkItemPriority WorkItemPriority + { + get + { + return _workItem._workItemInfo.WorkItemPriority; + } + } + + /// <summary> + /// Return the result, same as GetResult() + /// </summary> + public object Result + { + get { return GetResult(); } + } + + /// <summary> + /// Returns the exception if occured otherwise returns null. + /// This value is valid only after the work item completed, + /// before that it is always null. + /// </summary> + public object Exception + { + get { return _workItem._exception; } + } + + #endregion + + #region IInternalWorkItemResult Members + + public event WorkItemStateCallback OnWorkItemStarted + { + add + { + _workItem.OnWorkItemStarted += value; + } + remove + { + _workItem.OnWorkItemStarted -= value; + } + } + + + public event WorkItemStateCallback OnWorkItemCompleted + { + add + { + _workItem.OnWorkItemCompleted += value; + } + remove + { + _workItem.OnWorkItemCompleted -= value; + } + } + + #endregion + + #region IInternalWorkItemResult Members + + public IWorkItemResult GetWorkItemResult() + { + return this; + } + + public IWorkItemResult<TResult> GetWorkItemResultT<TResult>() + { + return new WorkItemResultTWrapper<TResult>(this); + } + + #endregion + } + + #endregion + + } +} diff --git a/Networking/SmartThreadPool/WorkItem.cs b/Networking/SmartThreadPool/WorkItem.cs new file mode 100644 index 0000000..0dc40d6 --- /dev/null +++ b/Networking/SmartThreadPool/WorkItem.cs @@ -0,0 +1,994 @@ +using System; +using System.Threading; +using System.Diagnostics; + +using Amib.Threading; + +namespace Amib.Threading.Internal +{ + /// <summary> + /// Holds a callback delegate and the state for that delegate. + /// </summary> + public partial class WorkItem : IHasWorkItemPriority + { + #region WorkItemState enum + + /// <summary> + /// Indicates the state of the work item in the thread pool + /// </summary> + private enum WorkItemState + { + InQueue = 0, // Nexts: InProgress, Canceled + InProgress = 1, // Nexts: Completed, Canceled + Completed = 2, // Stays Completed + Canceled = 3, // Stays Canceled + } + + private static bool IsValidStatesTransition(WorkItemState currentState, WorkItemState nextState) + { + bool valid = false; + + switch (currentState) + { + case WorkItemState.InQueue: + valid = (WorkItemState.InProgress == nextState) || (WorkItemState.Canceled == nextState); + break; + case WorkItemState.InProgress: + valid = (WorkItemState.Completed == nextState) || (WorkItemState.Canceled == nextState); + break; + case WorkItemState.Completed: + case WorkItemState.Canceled: + // Cannot be changed + break; + default: + // Unknown state + Debug.Assert(false); + break; + } + + return valid; + } + + #endregion + + #region Fields + + /// <summary> + /// Callback delegate for the callback. + /// </summary> + private readonly WorkItemCallback _callback; + + /// <summary> + /// State with which to call the callback delegate. + /// </summary> + private object _state; + +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + /// <summary> + /// Stores the caller's context + /// </summary> + private readonly CallerThreadContext _callerContext; +#endif + /// <summary> + /// Holds the result of the mehtod + /// </summary> + private object _result; + + /// <summary> + /// Hold the exception if the method threw it + /// </summary> + private Exception _exception; + + /// <summary> + /// Hold the state of the work item + /// </summary> + private WorkItemState _workItemState; + + /// <summary> + /// A ManualResetEvent to indicate that the result is ready + /// </summary> + private ManualResetEvent _workItemCompleted; + + /// <summary> + /// A reference count to the _workItemCompleted. + /// When it reaches to zero _workItemCompleted is Closed + /// </summary> + private int _workItemCompletedRefCount; + + /// <summary> + /// Represents the result state of the work item + /// </summary> + private readonly WorkItemResult _workItemResult; + + /// <summary> + /// Work item info + /// </summary> + private readonly WorkItemInfo _workItemInfo; + + /// <summary> + /// Called when the WorkItem starts + /// </summary> + private event WorkItemStateCallback _workItemStartedEvent; + + /// <summary> + /// Called when the WorkItem completes + /// </summary> + private event WorkItemStateCallback _workItemCompletedEvent; + + /// <summary> + /// A reference to an object that indicates whatever the + /// WorkItemsGroup has been canceled + /// </summary> + private CanceledWorkItemsGroup _canceledWorkItemsGroup = CanceledWorkItemsGroup.NotCanceledWorkItemsGroup; + + /// <summary> + /// A reference to an object that indicates whatever the + /// SmartThreadPool has been canceled + /// </summary> + private CanceledWorkItemsGroup _canceledSmartThreadPool = CanceledWorkItemsGroup.NotCanceledWorkItemsGroup; + + /// <summary> + /// The work item group this work item belong to. + /// </summary> + private readonly IWorkItemsGroup _workItemsGroup; + + /// <summary> + /// The thread that executes this workitem. + /// This field is available for the period when the work item is executed, before and after it is null. + /// </summary> + private Thread _executingThread; + + /// <summary> + /// The absulote time when the work item will be timeout + /// </summary> + private long _expirationTime; + + #region Performance Counter fields + + + + + /// <summary> + /// Stores how long the work item waited on the stp queue + /// </summary> + private Stopwatch _waitingOnQueueStopwatch; + + /// <summary> + /// Stores how much time it took the work item to execute after it went out of the queue + /// </summary> + private Stopwatch _processingStopwatch; + + #endregion + + #endregion + + #region Properties + + public TimeSpan WaitingTime + { + get + { + return _waitingOnQueueStopwatch.Elapsed; + } + } + + public TimeSpan ProcessTime + { + get + { + return _processingStopwatch.Elapsed; + } + } + + internal WorkItemInfo WorkItemInfo + { + get + { + return _workItemInfo; + } + } + + #endregion + + #region Construction + + /// <summary> + /// Initialize the callback holding object. + /// </summary> + /// <param name="workItemsGroup">The workItemGroup of the workitem</param> + /// <param name="workItemInfo">The WorkItemInfo of te workitem</param> + /// <param name="callback">Callback delegate for the callback.</param> + /// <param name="state">State with which to call the callback delegate.</param> + /// + /// We assume that the WorkItem object is created within the thread + /// that meant to run the callback + public WorkItem( + IWorkItemsGroup workItemsGroup, + WorkItemInfo workItemInfo, + WorkItemCallback callback, + object state) + { + _workItemsGroup = workItemsGroup; + _workItemInfo = workItemInfo; + +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + if (_workItemInfo.UseCallerCallContext || _workItemInfo.UseCallerHttpContext) + { + _callerContext = CallerThreadContext.Capture(_workItemInfo.UseCallerCallContext, _workItemInfo.UseCallerHttpContext); + } +#endif + + _callback = callback; + _state = state; + _workItemResult = new WorkItemResult(this); + Initialize(); + } + + internal void Initialize() + { + // The _workItemState is changed directly instead of using the SetWorkItemState + // method since we don't want to go throught IsValidStateTransition. + _workItemState = WorkItemState.InQueue; + + _workItemCompleted = null; + _workItemCompletedRefCount = 0; + _waitingOnQueueStopwatch = new Stopwatch(); + _processingStopwatch = new Stopwatch(); + _expirationTime = + _workItemInfo.Timeout > 0 ? + DateTime.UtcNow.Ticks + _workItemInfo.Timeout * TimeSpan.TicksPerMillisecond : + long.MaxValue; + } + + internal bool WasQueuedBy(IWorkItemsGroup workItemsGroup) + { + return (workItemsGroup == _workItemsGroup); + } + + + #endregion + + #region Methods + + internal CanceledWorkItemsGroup CanceledWorkItemsGroup + { + get { return _canceledWorkItemsGroup; } + set { _canceledWorkItemsGroup = value; } + } + + internal CanceledWorkItemsGroup CanceledSmartThreadPool + { + get { return _canceledSmartThreadPool; } + set { _canceledSmartThreadPool = value; } + } + + /// <summary> + /// Change the state of the work item to in progress if it wasn't canceled. + /// </summary> + /// <returns> + /// Return true on success or false in case the work item was canceled. + /// If the work item needs to run a post execute then the method will return true. + /// </returns> + public bool StartingWorkItem() + { + _waitingOnQueueStopwatch.Stop(); + _processingStopwatch.Start(); + + lock (this) + { + if (IsCanceled) + { + bool result = false; + if ((_workItemInfo.PostExecuteWorkItemCallback != null) && + ((_workItemInfo.CallToPostExecute & CallToPostExecute.WhenWorkItemCanceled) == CallToPostExecute.WhenWorkItemCanceled)) + { + result = true; + } + + return result; + } + + Debug.Assert(WorkItemState.InQueue == GetWorkItemState()); + + // No need for a lock yet, only after the state has changed to InProgress + _executingThread = Thread.CurrentThread; + + SetWorkItemState(WorkItemState.InProgress); + } + + return true; + } + + /// <summary> + /// Execute the work item and the post execute + /// </summary> + public void Execute() + { + CallToPostExecute currentCallToPostExecute = 0; + + // Execute the work item if we are in the correct state + switch (GetWorkItemState()) + { + case WorkItemState.InProgress: + currentCallToPostExecute |= CallToPostExecute.WhenWorkItemNotCanceled; + ExecuteWorkItem(); + break; + case WorkItemState.Canceled: + currentCallToPostExecute |= CallToPostExecute.WhenWorkItemCanceled; + break; + default: + Debug.Assert(false); + throw new NotSupportedException(); + } + + // Run the post execute as needed + if ((currentCallToPostExecute & _workItemInfo.CallToPostExecute) != 0) + { + PostExecute(); + } + + _processingStopwatch.Stop(); + } + + internal void FireWorkItemCompleted() + { + try + { + if (null != _workItemCompletedEvent) + { + _workItemCompletedEvent(this); + } + } + catch // Suppress exceptions + { } + } + + internal void FireWorkItemStarted() + { + try + { + if (null != _workItemStartedEvent) + { + _workItemStartedEvent(this); + } + } + catch // Suppress exceptions + { } + } + + /// <summary> + /// Execute the work item + /// </summary> + private void ExecuteWorkItem() + { + +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + CallerThreadContext ctc = null; + if (null != _callerContext) + { + ctc = CallerThreadContext.Capture(_callerContext.CapturedCallContext, _callerContext.CapturedHttpContext); + CallerThreadContext.Apply(_callerContext); + } +#endif + + Exception exception = null; + object result = null; + + try + { + try + { + result = _callback(_state); + } + catch (Exception e) + { + // Save the exception so we can rethrow it later + exception = e; + } + + // Remove the value of the execution thread, so it will be impossible to cancel the work item, + // since it is already completed. + // Cancelling a work item that already completed may cause the abortion of the next work item!!! + Thread executionThread = Interlocked.CompareExchange(ref _executingThread, null, _executingThread); + + if (null == executionThread) + { + // Oops! we are going to be aborted..., Wait here so we can catch the ThreadAbortException + Thread.Sleep(60 * 1000); + + // If after 1 minute this thread was not aborted then let it continue working. + } + } + // We must treat the ThreadAbortException or else it will be stored in the exception variable + catch (ThreadAbortException tae) + { + tae.GetHashCode(); + // Check if the work item was cancelled + // If we got a ThreadAbortException and the STP is not shutting down, it means the + // work items was cancelled. + if (!SmartThreadPool.CurrentThreadEntry.AssociatedSmartThreadPool.IsShuttingdown) + { +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + Thread.ResetAbort(); +#endif + } + } + +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + if (null != _callerContext) + { + CallerThreadContext.Apply(ctc); + } +#endif + + if (!SmartThreadPool.IsWorkItemCanceled) + { + SetResult(result, exception); + } + } + + /// <summary> + /// Runs the post execute callback + /// </summary> + private void PostExecute() + { + if (null != _workItemInfo.PostExecuteWorkItemCallback) + { + try + { + _workItemInfo.PostExecuteWorkItemCallback(_workItemResult); + } + catch (Exception e) + { + Debug.Assert(null != e); + } + } + } + + /// <summary> + /// Set the result of the work item to return + /// </summary> + /// <param name="result">The result of the work item</param> + /// <param name="exception">The exception that was throw while the workitem executed, null + /// if there was no exception.</param> + internal void SetResult(object result, Exception exception) + { + _result = result; + _exception = exception; + SignalComplete(false); + } + + /// <summary> + /// Returns the work item result + /// </summary> + /// <returns>The work item result</returns> + internal IWorkItemResult GetWorkItemResult() + { + return _workItemResult; + } + + /// <summary> + /// Wait for all work items to complete + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param> + /// <returns> + /// true when every work item in waitableResults has completed; otherwise false. + /// </returns> + internal static bool WaitAll( + IWaitableResult[] waitableResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + if (0 == waitableResults.Length) + { + return true; + } + + bool success; + WaitHandle[] waitHandles = new WaitHandle[waitableResults.Length]; + GetWaitHandles(waitableResults, waitHandles); + + if ((null == cancelWaitHandle) && (waitHandles.Length <= 64)) + { + success = STPEventWaitHandle.WaitAll(waitHandles, millisecondsTimeout, exitContext); + } + else + { + success = true; + int millisecondsLeft = millisecondsTimeout; + Stopwatch stopwatch = Stopwatch.StartNew(); + + WaitHandle[] whs; + if (null != cancelWaitHandle) + { + whs = new WaitHandle[] { null, cancelWaitHandle }; + } + else + { + whs = new WaitHandle[] { null }; + } + + bool waitInfinitely = (Timeout.Infinite == millisecondsTimeout); + // Iterate over the wait handles and wait for each one to complete. + // We cannot use WaitHandle.WaitAll directly, because the cancelWaitHandle + // won't affect it. + // Each iteration we update the time left for the timeout. + for (int i = 0; i < waitableResults.Length; ++i) + { + // WaitAny don't work with negative numbers + if (!waitInfinitely && (millisecondsLeft < 0)) + { + success = false; + break; + } + + whs[0] = waitHandles[i]; + int result = STPEventWaitHandle.WaitAny(whs, millisecondsLeft, exitContext); + if ((result > 0) || (STPEventWaitHandle.WaitTimeout == result)) + { + success = false; + break; + } + + if (!waitInfinitely) + { + // Update the time left to wait + millisecondsLeft = millisecondsTimeout - (int)stopwatch.ElapsedMilliseconds; + } + } + } + // Release the wait handles + ReleaseWaitHandles(waitableResults); + + return success; + } + + /// <summary> + /// Waits for any of the work items in the specified array to complete, cancel, or timeout + /// </summary> + /// <param name="waitableResults">Array of work item result objects</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or Timeout.Infinite (-1) to wait indefinitely.</param> + /// <param name="exitContext"> + /// true to exit the synchronization domain for the context before the wait (if in a synchronized context), and reacquire it; otherwise, false. + /// </param> + /// <param name="cancelWaitHandle">A cancel wait handle to interrupt the wait if needed</param> + /// <returns> + /// The array index of the work item result that satisfied the wait, or WaitTimeout if no work item result satisfied the wait and a time interval equivalent to millisecondsTimeout has passed or the work item has been canceled. + /// </returns> + internal static int WaitAny( + IWaitableResult[] waitableResults, + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + WaitHandle[] waitHandles; + + if (null != cancelWaitHandle) + { + waitHandles = new WaitHandle[waitableResults.Length + 1]; + GetWaitHandles(waitableResults, waitHandles); + waitHandles[waitableResults.Length] = cancelWaitHandle; + } + else + { + waitHandles = new WaitHandle[waitableResults.Length]; + GetWaitHandles(waitableResults, waitHandles); + } + + int result = STPEventWaitHandle.WaitAny(waitHandles, millisecondsTimeout, exitContext); + + // Treat cancel as timeout + if (null != cancelWaitHandle) + { + if (result == waitableResults.Length) + { + result = STPEventWaitHandle.WaitTimeout; + } + } + + ReleaseWaitHandles(waitableResults); + + return result; + } + + /// <summary> + /// Fill an array of wait handles with the work items wait handles. + /// </summary> + /// <param name="waitableResults">An array of work item results</param> + /// <param name="waitHandles">An array of wait handles to fill</param> + private static void GetWaitHandles( + IWaitableResult[] waitableResults, + WaitHandle[] waitHandles) + { + for (int i = 0; i < waitableResults.Length; ++i) + { + WorkItemResult wir = waitableResults[i].GetWorkItemResult() as WorkItemResult; + Debug.Assert(null != wir, "All waitableResults must be WorkItemResult objects"); + + waitHandles[i] = wir.GetWorkItem().GetWaitHandle(); + } + } + + /// <summary> + /// Release the work items' wait handles + /// </summary> + /// <param name="waitableResults">An array of work item results</param> + private static void ReleaseWaitHandles(IWaitableResult[] waitableResults) + { + for (int i = 0; i < waitableResults.Length; ++i) + { + WorkItemResult wir = (WorkItemResult)waitableResults[i].GetWorkItemResult(); + + wir.GetWorkItem().ReleaseWaitHandle(); + } + } + + #endregion + + #region Private Members + + private WorkItemState GetWorkItemState() + { + lock (this) + { + if (WorkItemState.Completed == _workItemState) + { + return _workItemState; + } + + long nowTicks = DateTime.UtcNow.Ticks; + + if (WorkItemState.Canceled != _workItemState && nowTicks > _expirationTime) + { + _workItemState = WorkItemState.Canceled; + } + + if (WorkItemState.InProgress == _workItemState) + { + return _workItemState; + } + + if (CanceledSmartThreadPool.IsCanceled || CanceledWorkItemsGroup.IsCanceled) + { + return WorkItemState.Canceled; + } + + return _workItemState; + } + } + + + /// <summary> + /// Sets the work item's state + /// </summary> + /// <param name="workItemState">The state to set the work item to</param> + private void SetWorkItemState(WorkItemState workItemState) + { + lock (this) + { + if (IsValidStatesTransition(_workItemState, workItemState)) + { + _workItemState = workItemState; + } + } + } + + /// <summary> + /// Signals that work item has been completed or canceled + /// </summary> + /// <param name="canceled">Indicates that the work item has been canceled</param> + private void SignalComplete(bool canceled) + { + SetWorkItemState(canceled ? WorkItemState.Canceled : WorkItemState.Completed); + lock (this) + { + // If someone is waiting then signal. + if (null != _workItemCompleted) + { + _workItemCompleted.Set(); + } + } + } + + internal void WorkItemIsQueued() + { + _waitingOnQueueStopwatch.Start(); + } + + #endregion + + #region Members exposed by WorkItemResult + + /// <summary> + /// Cancel the work item if it didn't start running yet. + /// </summary> + /// <returns>Returns true on success or false if the work item is in progress or already completed</returns> + private bool Cancel(bool abortExecution) + { +#if (_WINDOWS_CE) + if(abortExecution) + { + throw new ArgumentOutOfRangeException("abortExecution", "WindowsCE doesn't support this feature"); + } +#endif + bool success = false; + bool signalComplete = false; + + lock (this) + { + switch (GetWorkItemState()) + { + case WorkItemState.Canceled: + //Debug.WriteLine("Work item already canceled"); + if (abortExecution) + { + Thread executionThread = Interlocked.CompareExchange(ref _executingThread, null, _executingThread); + if (null != executionThread) + { + executionThread.Abort(); // "Cancel" + // No need to signalComplete, because we already cancelled this work item + // so it already signaled its completion. + //signalComplete = true; + } + } + success = true; + break; + case WorkItemState.Completed: + //Debug.WriteLine("Work item cannot be canceled"); + break; + case WorkItemState.InProgress: + if (abortExecution) + { + Thread executionThread = Interlocked.CompareExchange(ref _executingThread, null, _executingThread); + if (null != executionThread) + { + executionThread.Abort(); // "Cancel" + success = true; + signalComplete = true; + } + } + else + { + success = true; + signalComplete = true; + } + break; + case WorkItemState.InQueue: + // Signal to the wait for completion that the work + // item has been completed (canceled). There is no + // reason to wait for it to get out of the queue + signalComplete = true; + //Debug.WriteLine("Work item canceled"); + success = true; + break; + } + + if (signalComplete) + { + SignalComplete(true); + } + } + return success; + } + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits for the result, timeout, or cancel. + /// In case of error the method throws and exception + /// </summary> + /// <returns>The result of the work item</returns> + private object GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle) + { + Exception e; + object result = GetResult(millisecondsTimeout, exitContext, cancelWaitHandle, out e); + if (null != e) + { + throw new WorkItemResultException("The work item caused an excpetion, see the inner exception for details", e); + } + return result; + } + + /// <summary> + /// Get the result of the work item. + /// If the work item didn't run yet then the caller waits for the result, timeout, or cancel. + /// In case of error the e argument is filled with the exception + /// </summary> + /// <returns>The result of the work item</returns> + private object GetResult( + int millisecondsTimeout, + bool exitContext, + WaitHandle cancelWaitHandle, + out Exception e) + { + e = null; + + // Check for cancel + if (WorkItemState.Canceled == GetWorkItemState()) + { + throw new WorkItemCancelException("Work item canceled"); + } + + // Check for completion + if (IsCompleted) + { + e = _exception; + return _result; + } + + // If no cancelWaitHandle is provided + if (null == cancelWaitHandle) + { + WaitHandle wh = GetWaitHandle(); + + bool timeout = !STPEventWaitHandle.WaitOne(wh, millisecondsTimeout, exitContext); + + ReleaseWaitHandle(); + + if (timeout) + { + throw new WorkItemTimeoutException("Work item timeout"); + } + } + else + { + WaitHandle wh = GetWaitHandle(); + int result = STPEventWaitHandle.WaitAny(new WaitHandle[] { wh, cancelWaitHandle }); + ReleaseWaitHandle(); + + switch (result) + { + case 0: + // The work item signaled + // Note that the signal could be also as a result of canceling the + // work item (not the get result) + break; + case 1: + case STPEventWaitHandle.WaitTimeout: + throw new WorkItemTimeoutException("Work item timeout"); + default: + Debug.Assert(false); + break; + + } + } + + // Check for cancel + if (WorkItemState.Canceled == GetWorkItemState()) + { + throw new WorkItemCancelException("Work item canceled"); + } + + Debug.Assert(IsCompleted); + + e = _exception; + + // Return the result + return _result; + } + + /// <summary> + /// A wait handle to wait for completion, cancel, or timeout + /// </summary> + private WaitHandle GetWaitHandle() + { + lock (this) + { + if (null == _workItemCompleted) + { + _workItemCompleted = EventWaitHandleFactory.CreateManualResetEvent(IsCompleted); + } + ++_workItemCompletedRefCount; + } + return _workItemCompleted; + } + + private void ReleaseWaitHandle() + { + lock (this) + { + if (null != _workItemCompleted) + { + --_workItemCompletedRefCount; + if (0 == _workItemCompletedRefCount) + { + _workItemCompleted.Close(); + _workItemCompleted = null; + } + } + } + } + + /// <summary> + /// Returns true when the work item has completed or canceled + /// </summary> + private bool IsCompleted + { + get + { + lock (this) + { + WorkItemState workItemState = GetWorkItemState(); + return ((workItemState == WorkItemState.Completed) || + (workItemState == WorkItemState.Canceled)); + } + } + } + + /// <summary> + /// Returns true when the work item has canceled + /// </summary> + public bool IsCanceled + { + get + { + lock (this) + { + return (GetWorkItemState() == WorkItemState.Canceled); + } + } + } + + #endregion + + #region IHasWorkItemPriority Members + + /// <summary> + /// Returns the priority of the work item + /// </summary> + public WorkItemPriority WorkItemPriority + { + get + { + return _workItemInfo.WorkItemPriority; + } + } + + #endregion + + internal event WorkItemStateCallback OnWorkItemStarted + { + add + { + _workItemStartedEvent += value; + } + remove + { + _workItemStartedEvent -= value; + } + } + + internal event WorkItemStateCallback OnWorkItemCompleted + { + add + { + _workItemCompletedEvent += value; + } + remove + { + _workItemCompletedEvent -= value; + } + } + + public void DisposeOfState() + { + if (_workItemInfo.DisposeOfStateObjects) + { + IDisposable disp = _state as IDisposable; + if (null != disp) + { + disp.Dispose(); + _state = null; + } + } + } + } +} diff --git a/Networking/SmartThreadPool/WorkItemFactory.cs b/Networking/SmartThreadPool/WorkItemFactory.cs new file mode 100644 index 0000000..16ccd81 --- /dev/null +++ b/Networking/SmartThreadPool/WorkItemFactory.cs @@ -0,0 +1,343 @@ +using System; + +namespace Amib.Threading.Internal +{ + #region WorkItemFactory class + + public class WorkItemFactory + { + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The WorkItemsGroup of this workitem</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback) + { + return CreateWorkItem(workItemsGroup, wigStartInfo, callback, null); + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The WorkItemsGroup of this workitem</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="workItemPriority">The priority of the work item</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + WorkItemPriority workItemPriority) + { + return CreateWorkItem(workItemsGroup, wigStartInfo, callback, null, workItemPriority); + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The WorkItemsGroup of this workitem</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="workItemInfo">Work item info</param> + /// <param name="callback">A callback to execute</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemInfo workItemInfo, + WorkItemCallback callback) + { + return CreateWorkItem( + workItemsGroup, + wigStartInfo, + workItemInfo, + callback, + null); + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The WorkItemsGroup of this workitem</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state) + { + ValidateCallback(callback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = wigStartInfo.PostExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = wigStartInfo.WorkItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + return workItem; + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The work items group</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + WorkItemPriority workItemPriority) + { + ValidateCallback(callback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = wigStartInfo.PostExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = workItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The work items group</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="workItemInfo">Work item information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemInfo workItemInfo, + WorkItemCallback callback, + object state) + { + ValidateCallback(callback); + ValidateCallback(workItemInfo.PostExecuteWorkItemCallback); + + WorkItem workItem = new WorkItem( + workItemsGroup, + new WorkItemInfo(workItemInfo), + callback, + state); + + return workItem; + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The work items group</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback) + { + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = wigStartInfo.WorkItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The work items group</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + WorkItemPriority workItemPriority) + { + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = wigStartInfo.CallToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = workItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The work items group</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="callToPostExecute">Indicates on which cases to call to the post execute callback</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute) + { + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = callToPostExecute; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + workItemInfo.WorkItemPriority = wigStartInfo.WorkItemPriority; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + /// <summary> + /// Create a new work item + /// </summary> + /// <param name="workItemsGroup">The work items group</param> + /// <param name="wigStartInfo">Work item group start information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="callToPostExecute">Indicates on which cases to call to the post execute callback</param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item</returns> + public static WorkItem CreateWorkItem( + IWorkItemsGroup workItemsGroup, + WIGStartInfo wigStartInfo, + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute, + WorkItemPriority workItemPriority) + { + + ValidateCallback(callback); + ValidateCallback(postExecuteWorkItemCallback); + + WorkItemInfo workItemInfo = new WorkItemInfo(); + workItemInfo.UseCallerCallContext = wigStartInfo.UseCallerCallContext; + workItemInfo.UseCallerHttpContext = wigStartInfo.UseCallerHttpContext; + workItemInfo.PostExecuteWorkItemCallback = postExecuteWorkItemCallback; + workItemInfo.CallToPostExecute = callToPostExecute; + workItemInfo.WorkItemPriority = workItemPriority; + workItemInfo.DisposeOfStateObjects = wigStartInfo.DisposeOfStateObjects; + + WorkItem workItem = new WorkItem( + workItemsGroup, + workItemInfo, + callback, + state); + + return workItem; + } + + private static void ValidateCallback(Delegate callback) + { + if (callback != null && callback.GetInvocationList().Length > 1) + { + throw new NotSupportedException("SmartThreadPool doesn't support delegates chains"); + } + } + } + + #endregion +} diff --git a/Networking/SmartThreadPool/WorkItemInfo.cs b/Networking/SmartThreadPool/WorkItemInfo.cs new file mode 100644 index 0000000..0d7fc85 --- /dev/null +++ b/Networking/SmartThreadPool/WorkItemInfo.cs @@ -0,0 +1,69 @@ +namespace Amib.Threading +{ + #region WorkItemInfo class + + /// <summary> + /// Summary description for WorkItemInfo. + /// </summary> + public class WorkItemInfo + { + public WorkItemInfo() + { + UseCallerCallContext = SmartThreadPool.DefaultUseCallerCallContext; + UseCallerHttpContext = SmartThreadPool.DefaultUseCallerHttpContext; + DisposeOfStateObjects = SmartThreadPool.DefaultDisposeOfStateObjects; + CallToPostExecute = SmartThreadPool.DefaultCallToPostExecute; + PostExecuteWorkItemCallback = SmartThreadPool.DefaultPostExecuteWorkItemCallback; + WorkItemPriority = SmartThreadPool.DefaultWorkItemPriority; + } + + public WorkItemInfo(WorkItemInfo workItemInfo) + { + UseCallerCallContext = workItemInfo.UseCallerCallContext; + UseCallerHttpContext = workItemInfo.UseCallerHttpContext; + DisposeOfStateObjects = workItemInfo.DisposeOfStateObjects; + CallToPostExecute = workItemInfo.CallToPostExecute; + PostExecuteWorkItemCallback = workItemInfo.PostExecuteWorkItemCallback; + WorkItemPriority = workItemInfo.WorkItemPriority; + Timeout = workItemInfo.Timeout; + } + + /// <summary> + /// Get/Set if to use the caller's security context + /// </summary> + public bool UseCallerCallContext { get; set; } + + /// <summary> + /// Get/Set if to use the caller's HTTP context + /// </summary> + public bool UseCallerHttpContext { get; set; } + + /// <summary> + /// Get/Set if to dispose of the state object of a work item + /// </summary> + public bool DisposeOfStateObjects { get; set; } + + /// <summary> + /// Get/Set the run the post execute options + /// </summary> + public CallToPostExecute CallToPostExecute { get; set; } + + /// <summary> + /// Get/Set the post execute callback + /// </summary> + public PostExecuteWorkItemCallback PostExecuteWorkItemCallback { get; set; } + + /// <summary> + /// Get/Set the work item's priority + /// </summary> + public WorkItemPriority WorkItemPriority { get; set; } + + /// <summary> + /// Get/Set the work item's timout in milliseconds. + /// This is a passive timout. When the timout expires the work item won't be actively aborted! + /// </summary> + public long Timeout { get; set; } + } + + #endregion +} diff --git a/Networking/SmartThreadPool/WorkItemResultTWrapper.cs b/Networking/SmartThreadPool/WorkItemResultTWrapper.cs new file mode 100644 index 0000000..fbd0c8b --- /dev/null +++ b/Networking/SmartThreadPool/WorkItemResultTWrapper.cs @@ -0,0 +1,128 @@ +using System; +using System.Threading; + +namespace Amib.Threading.Internal +{ + #region WorkItemResultTWrapper class + + internal class WorkItemResultTWrapper<TResult> : IWorkItemResult<TResult>, IInternalWaitableResult + { + private readonly IWorkItemResult _workItemResult; + + public WorkItemResultTWrapper(IWorkItemResult workItemResult) + { + _workItemResult = workItemResult; + } + + #region IWorkItemResult<TResult> Members + + public TResult GetResult() + { + return (TResult)_workItemResult.GetResult(); + } + + public TResult GetResult(int millisecondsTimeout, bool exitContext) + { + return (TResult)_workItemResult.GetResult(millisecondsTimeout, exitContext); + } + + public TResult GetResult(TimeSpan timeout, bool exitContext) + { + return (TResult)_workItemResult.GetResult(timeout, exitContext); + } + + public TResult GetResult(int millisecondsTimeout, bool exitContext, WaitHandle cancelWaitHandle) + { + return (TResult)_workItemResult.GetResult(millisecondsTimeout, exitContext, cancelWaitHandle); + } + + public TResult GetResult(TimeSpan timeout, bool exitContext, WaitHandle cancelWaitHandle) + { + return (TResult)_workItemResult.GetResult(timeout, exitContext, cancelWaitHandle); + } + + public TResult GetResult(out Exception e) + { + return (TResult)_workItemResult.GetResult(out e); + } + + public TResult GetResult(int millisecondsTimeout, bool exitContext, out Exception e) + { + return (TResult)_workItemResult.GetResult(millisecondsTimeout, exitContext, out e); + } + + public TResult GetResult(TimeSpan timeout, bool exitContext, out Exception e) + { + return (TResult)_workItemResult.GetResult(timeout, exitContext, out e); + } + + public TResult GetResult(int millisecondsTimeout, bool exitContext, WaitHandle cancelWaitHandle, out Exception e) + { + return (TResult)_workItemResult.GetResult(millisecondsTimeout, exitContext, cancelWaitHandle, out e); + } + + public TResult GetResult(TimeSpan timeout, bool exitContext, WaitHandle cancelWaitHandle, out Exception e) + { + return (TResult)_workItemResult.GetResult(timeout, exitContext, cancelWaitHandle, out e); + } + + public bool IsCompleted + { + get { return _workItemResult.IsCompleted; } + } + + public bool IsCanceled + { + get { return _workItemResult.IsCanceled; } + } + + public object State + { + get { return _workItemResult.State; } + } + + public bool Cancel() + { + return _workItemResult.Cancel(); + } + + public bool Cancel(bool abortExecution) + { + return _workItemResult.Cancel(abortExecution); + } + + public WorkItemPriority WorkItemPriority + { + get { return _workItemResult.WorkItemPriority; } + } + + public TResult Result + { + get { return (TResult)_workItemResult.Result; } + } + + public object Exception + { + get { return _workItemResult.Exception; } + } + + #region IInternalWorkItemResult Members + + public IWorkItemResult GetWorkItemResult() + { + return _workItemResult.GetWorkItemResult(); + } + + public IWorkItemResult<TRes> GetWorkItemResultT<TRes>() + { + return (IWorkItemResult<TRes>)this; + } + + #endregion + + #endregion + } + + #endregion + +} diff --git a/Networking/SmartThreadPool/WorkItemsGroup.cs b/Networking/SmartThreadPool/WorkItemsGroup.cs new file mode 100644 index 0000000..a78c78c --- /dev/null +++ b/Networking/SmartThreadPool/WorkItemsGroup.cs @@ -0,0 +1,369 @@ +using System; +using System.Threading; +using System.Runtime.CompilerServices; +using System.Diagnostics; + +namespace Amib.Threading.Internal +{ + + #region WorkItemsGroup class + + /// <summary> + /// Summary description for WorkItemsGroup. + /// </summary> + public class WorkItemsGroup : WorkItemsGroupBase + { + #region Private members + + private readonly object _lock = new object(); + + /// <summary> + /// A reference to the SmartThreadPool instance that created this + /// WorkItemsGroup. + /// </summary> + private readonly SmartThreadPool _stp; + + /// <summary> + /// The OnIdle event + /// </summary> + private event WorkItemsGroupIdleHandler _onIdle; + + /// <summary> + /// A flag to indicate if the Work Items Group is now suspended. + /// </summary> + private bool _isSuspended; + + /// <summary> + /// Defines how many work items of this WorkItemsGroup can run at once. + /// </summary> + private int _concurrency; + + /// <summary> + /// Priority queue to hold work items before they are passed + /// to the SmartThreadPool. + /// </summary> + private readonly PriorityQueue _workItemsQueue; + + /// <summary> + /// Indicate how many work items are waiting in the SmartThreadPool + /// queue. + /// This value is used to apply the concurrency. + /// </summary> + private int _workItemsInStpQueue; + + /// <summary> + /// Indicate how many work items are currently running in the SmartThreadPool. + /// This value is used with the Cancel, to calculate if we can send new + /// work items to the STP. + /// </summary> + private int _workItemsExecutingInStp = 0; + + /// <summary> + /// WorkItemsGroup start information + /// </summary> + private readonly WIGStartInfo _workItemsGroupStartInfo; + + /// <summary> + /// Signaled when all of the WorkItemsGroup's work item completed. + /// </summary> + //private readonly ManualResetEvent _isIdleWaitHandle = new ManualResetEvent(true); + private readonly ManualResetEvent _isIdleWaitHandle = EventWaitHandleFactory.CreateManualResetEvent(true); + + /// <summary> + /// A common object for all the work items that this work items group + /// generate so we can mark them to cancel in O(1) + /// </summary> + private CanceledWorkItemsGroup _canceledWorkItemsGroup = new CanceledWorkItemsGroup(); + + #endregion + + #region Construction + + public WorkItemsGroup( + SmartThreadPool stp, + int concurrency, + WIGStartInfo wigStartInfo) + { + if (concurrency <= 0) + { + throw new ArgumentOutOfRangeException( + "concurrency", +#if !(_WINDOWS_CE) && !(_SILVERLIGHT) && !(WINDOWS_PHONE) + concurrency, +#endif + "concurrency must be greater than zero"); + } + _stp = stp; + _concurrency = concurrency; + _workItemsGroupStartInfo = new WIGStartInfo(wigStartInfo).AsReadOnly(); + _workItemsQueue = new PriorityQueue(); + Name = "WorkItemsGroup"; + + // The _workItemsInStpQueue gets the number of currently executing work items, + // because once a work item is executing, it cannot be cancelled. + _workItemsInStpQueue = _workItemsExecutingInStp; + + _isSuspended = _workItemsGroupStartInfo.StartSuspended; + } + + #endregion + + #region WorkItemsGroupBase Overrides + + public override int Concurrency + { + get { return _concurrency; } + set + { + Debug.Assert(value > 0); + + int diff = value - _concurrency; + _concurrency = value; + if (diff > 0) + { + EnqueueToSTPNextNWorkItem(diff); + } + } + } + + public override int InUseThreads + { + get + { + return _workItemsExecutingInStp; + } + } + + public override int WaitingCallbacks + { + get { return _workItemsQueue.Count; } + } + + public override object[] GetStates() + { + lock (_lock) + { + object[] states = new object[_workItemsQueue.Count]; + int i = 0; + foreach (WorkItem workItem in _workItemsQueue) + { + states[i] = workItem.GetWorkItemResult().State; + ++i; + } + return states; + } + } + + /// <summary> + /// WorkItemsGroup start information + /// </summary> + public override WIGStartInfo WIGStartInfo + { + get { return _workItemsGroupStartInfo; } + } + + /// <summary> + /// Start the Work Items Group if it was started suspended + /// </summary> + public override void Start() + { + // If the Work Items Group already started then quit + if (!_isSuspended) + { + return; + } + _isSuspended = false; + + EnqueueToSTPNextNWorkItem(Math.Min(_workItemsQueue.Count, _concurrency)); + } + + public override void Cancel(bool abortExecution) + { + lock (_lock) + { + _canceledWorkItemsGroup.IsCanceled = true; + _workItemsQueue.Clear(); + _workItemsInStpQueue = 0; + _canceledWorkItemsGroup = new CanceledWorkItemsGroup(); + } + + if (abortExecution) + { + _stp.CancelAbortWorkItemsGroup(this); + } + } + + /// <summary> + /// Wait for the thread pool to be idle + /// </summary> + public override bool WaitForIdle(int millisecondsTimeout) + { + SmartThreadPool.ValidateWorkItemsGroupWaitForIdle(this); + return STPEventWaitHandle.WaitOne(_isIdleWaitHandle, millisecondsTimeout, false); + } + + public override event WorkItemsGroupIdleHandler OnIdle + { + add { _onIdle += value; } + remove { _onIdle -= value; } + } + + #endregion + + #region Private methods + + private void RegisterToWorkItemCompletion(IWorkItemResult wir) + { + IInternalWorkItemResult iwir = (IInternalWorkItemResult)wir; + iwir.OnWorkItemStarted += OnWorkItemStartedCallback; + iwir.OnWorkItemCompleted += OnWorkItemCompletedCallback; + } + + public void OnSTPIsStarting() + { + if (_isSuspended) + { + return; + } + + EnqueueToSTPNextNWorkItem(_concurrency); + } + + public void EnqueueToSTPNextNWorkItem(int count) + { + for (int i = 0; i < count; ++i) + { + EnqueueToSTPNextWorkItem(null, false); + } + } + + private object FireOnIdle(object state) + { + FireOnIdleImpl(_onIdle); + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void FireOnIdleImpl(WorkItemsGroupIdleHandler onIdle) + { + if(null == onIdle) + { + return; + } + + Delegate[] delegates = onIdle.GetInvocationList(); + foreach(WorkItemsGroupIdleHandler eh in delegates) + { + try + { + eh(this); + } + catch { } // Suppress exceptions + } + } + + private void OnWorkItemStartedCallback(WorkItem workItem) + { + lock(_lock) + { + ++_workItemsExecutingInStp; + } + } + + private void OnWorkItemCompletedCallback(WorkItem workItem) + { + EnqueueToSTPNextWorkItem(null, true); + } + + internal override void Enqueue(WorkItem workItem) + { + EnqueueToSTPNextWorkItem(workItem); + } + + private void EnqueueToSTPNextWorkItem(WorkItem workItem) + { + EnqueueToSTPNextWorkItem(workItem, false); + } + + private void EnqueueToSTPNextWorkItem(WorkItem workItem, bool decrementWorkItemsInStpQueue) + { + lock(_lock) + { + // Got here from OnWorkItemCompletedCallback() + if (decrementWorkItemsInStpQueue) + { + --_workItemsInStpQueue; + + if(_workItemsInStpQueue < 0) + { + _workItemsInStpQueue = 0; + } + + --_workItemsExecutingInStp; + + if(_workItemsExecutingInStp < 0) + { + _workItemsExecutingInStp = 0; + } + } + + // If the work item is not null then enqueue it + if (null != workItem) + { + workItem.CanceledWorkItemsGroup = _canceledWorkItemsGroup; + + RegisterToWorkItemCompletion(workItem.GetWorkItemResult()); + _workItemsQueue.Enqueue(workItem); + //_stp.IncrementWorkItemsCount(); + + if ((1 == _workItemsQueue.Count) && + (0 == _workItemsInStpQueue)) + { + _stp.RegisterWorkItemsGroup(this); + IsIdle = false; + _isIdleWaitHandle.Reset(); + } + } + + // If the work items queue of the group is empty than quit + if (0 == _workItemsQueue.Count) + { + if (0 == _workItemsInStpQueue) + { + _stp.UnregisterWorkItemsGroup(this); + IsIdle = true; + _isIdleWaitHandle.Set(); + if (decrementWorkItemsInStpQueue && _onIdle != null && _onIdle.GetInvocationList().Length > 0) + { + _stp.QueueWorkItem(new WorkItemCallback(FireOnIdle)); + } + } + return; + } + + if (!_isSuspended) + { + if (_workItemsInStpQueue < _concurrency) + { + WorkItem nextWorkItem = _workItemsQueue.Dequeue() as WorkItem; + try + { + _stp.Enqueue(nextWorkItem); + } + catch (ObjectDisposedException e) + { + e.GetHashCode(); + // The STP has been shutdown + } + + ++_workItemsInStpQueue; + } + } + } + } + + #endregion + } + + #endregion +} diff --git a/Networking/SmartThreadPool/WorkItemsGroupBase.cs b/Networking/SmartThreadPool/WorkItemsGroupBase.cs new file mode 100644 index 0000000..c6c0fdb --- /dev/null +++ b/Networking/SmartThreadPool/WorkItemsGroupBase.cs @@ -0,0 +1,449 @@ +using System; +using System.Threading; + +namespace Amib.Threading.Internal +{ + public abstract class WorkItemsGroupBase : IWorkItemsGroup + { + #region Private Fields + + /// <summary> + /// Contains the name of this instance of SmartThreadPool. + /// Can be changed by the user. + /// </summary> + private string _name = "WorkItemsGroupBase"; + + public WorkItemsGroupBase() + { + IsIdle = true; + } + + #endregion + + #region IWorkItemsGroup Members + + #region Public Methods + + /// <summary> + /// Get/Set the name of the SmartThreadPool/WorkItemsGroup instance + /// </summary> + public string Name + { + get { return _name; } + set { _name = value; } + } + + #endregion + + #region Abstract Methods + + public abstract int Concurrency { get; set; } + public abstract int WaitingCallbacks { get; } + public abstract int InUseThreads { get; } + + public abstract object[] GetStates(); + public abstract WIGStartInfo WIGStartInfo { get; } + public abstract void Start(); + public abstract void Cancel(bool abortExecution); + public abstract bool WaitForIdle(int millisecondsTimeout); + public abstract event WorkItemsGroupIdleHandler OnIdle; + + internal abstract void Enqueue(WorkItem workItem); + internal virtual void PreQueueWorkItem() { } + + #endregion + + #region Common Base Methods + + /// <summary> + /// Cancel all the work items. + /// Same as Cancel(false) + /// </summary> + public virtual void Cancel() + { + Cancel(false); + } + + /// <summary> + /// Wait for the SmartThreadPool/WorkItemsGroup to be idle + /// </summary> + public void WaitForIdle() + { + WaitForIdle(Timeout.Infinite); + } + + /// <summary> + /// Wait for the SmartThreadPool/WorkItemsGroup to be idle + /// </summary> + public bool WaitForIdle(TimeSpan timeout) + { + return WaitForIdle((int)timeout.TotalMilliseconds); + } + + /// <summary> + /// IsIdle is true when there are no work items running or queued. + /// </summary> + public bool IsIdle { get; protected set; } + + #endregion + + #region QueueWorkItem + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem(WorkItemCallback callback) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="workItemPriority">The priority of the work item</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, WorkItemPriority workItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="workItemInfo">Work item info</param> + /// <param name="callback">A callback to execute</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, workItemInfo, callback); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state) + { + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, state); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem(WorkItemCallback callback, object state, WorkItemPriority workItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, state, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="workItemInfo">Work item information</param> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem(WorkItemInfo workItemInfo, WorkItemCallback callback, object state) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, workItemInfo, callback, state); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, state, postExecuteWorkItemCallback); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + WorkItemPriority workItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, state, postExecuteWorkItemCallback, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="callToPostExecute">Indicates on which cases to call to the post execute callback</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, state, postExecuteWorkItemCallback, callToPostExecute); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + /// <summary> + /// Queue a work item + /// </summary> + /// <param name="callback">A callback to execute</param> + /// <param name="state"> + /// The context object of the work item. Used for passing arguments to the work item. + /// </param> + /// <param name="postExecuteWorkItemCallback"> + /// A delegate to call after the callback completion + /// </param> + /// <param name="callToPostExecute">Indicates on which cases to call to the post execute callback</param> + /// <param name="workItemPriority">The work item priority</param> + /// <returns>Returns a work item result</returns> + public IWorkItemResult QueueWorkItem( + WorkItemCallback callback, + object state, + PostExecuteWorkItemCallback postExecuteWorkItemCallback, + CallToPostExecute callToPostExecute, + WorkItemPriority workItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem(this, WIGStartInfo, callback, state, postExecuteWorkItemCallback, callToPostExecute, workItemPriority); + Enqueue(workItem); + return workItem.GetWorkItemResult(); + } + + #endregion + + #region QueueWorkItem(Action<...>) + + public IWorkItemResult QueueWorkItem(Action action, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem (); + WorkItem workItem = WorkItemFactory.CreateWorkItem ( + this, + WIGStartInfo, + delegate + { + action.Invoke (); + return null; + }, priority); + Enqueue (workItem); + return workItem.GetWorkItemResult (); + } + + public IWorkItemResult QueueWorkItem<T>(Action<T> action, T arg, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem (); + WorkItem workItem = WorkItemFactory.CreateWorkItem ( + this, + WIGStartInfo, + state => + { + action.Invoke (arg); + return null; + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg } : null, priority); + Enqueue (workItem); + return workItem.GetWorkItemResult (); + } + + public IWorkItemResult QueueWorkItem<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem (); + WorkItem workItem = WorkItemFactory.CreateWorkItem ( + this, + WIGStartInfo, + state => + { + action.Invoke (arg1, arg2); + return null; + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg1, arg2 } : null, priority); + Enqueue (workItem); + return workItem.GetWorkItemResult (); + } + + public IWorkItemResult QueueWorkItem<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem (); + WorkItem workItem = WorkItemFactory.CreateWorkItem ( + this, + WIGStartInfo, + state => + { + action.Invoke (arg1, arg2, arg3); + return null; + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg1, arg2, arg3 } : null, priority); + Enqueue (workItem); + return workItem.GetWorkItemResult (); + } + + public IWorkItemResult QueueWorkItem<T1, T2, T3, T4> ( + Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem (); + WorkItem workItem = WorkItemFactory.CreateWorkItem ( + this, + WIGStartInfo, + state => + { + action.Invoke (arg1, arg2, arg3, arg4); + return null; + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg1, arg2, arg3, arg4 } : null, priority); + Enqueue (workItem); + return workItem.GetWorkItemResult (); + } + + #endregion + + #region QueueWorkItem(Func<...>) + + public IWorkItemResult<TResult> QueueWorkItem<TResult>(Func<TResult> func, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem( + this, + WIGStartInfo, + state => + { + return func.Invoke(); + }, priority); + Enqueue(workItem); + return new WorkItemResultTWrapper<TResult>(workItem.GetWorkItemResult()); + } + + public IWorkItemResult<TResult> QueueWorkItem<T, TResult>(Func<T, TResult> func, T arg, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem( + this, + WIGStartInfo, + state => + { + return func.Invoke(arg); + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg } : null, + priority); + Enqueue(workItem); + return new WorkItemResultTWrapper<TResult>(workItem.GetWorkItemResult()); + } + + public IWorkItemResult<TResult> QueueWorkItem<T1, T2, TResult>(Func<T1, T2, TResult> func, T1 arg1, T2 arg2, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem( + this, + WIGStartInfo, + state => + { + return func.Invoke(arg1, arg2); + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg1, arg2 } : null, + priority); + Enqueue(workItem); + return new WorkItemResultTWrapper<TResult>(workItem.GetWorkItemResult()); + } + + public IWorkItemResult<TResult> QueueWorkItem<T1, T2, T3, TResult>( + Func<T1, T2, T3, TResult> func, T1 arg1, T2 arg2, T3 arg3, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem( + this, + WIGStartInfo, + state => + { + return func.Invoke(arg1, arg2, arg3); + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg1, arg2, arg3 } : null, + priority); + Enqueue(workItem); + return new WorkItemResultTWrapper<TResult>(workItem.GetWorkItemResult()); + } + + public IWorkItemResult<TResult> QueueWorkItem<T1, T2, T3, T4, TResult>( + Func<T1, T2, T3, T4, TResult> func, T1 arg1, T2 arg2, T3 arg3, T4 arg4, WorkItemPriority priority = SmartThreadPool.DefaultWorkItemPriority) + { + PreQueueWorkItem(); + WorkItem workItem = WorkItemFactory.CreateWorkItem( + this, + WIGStartInfo, + state => + { + return func.Invoke(arg1, arg2, arg3, arg4); + }, + WIGStartInfo.FillStateWithArgs ? new object[] { arg1, arg2, arg3, arg4 } : null, + priority); + Enqueue(workItem); + return new WorkItemResultTWrapper<TResult>(workItem.GetWorkItemResult()); + } + + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/Networking/SmartThreadPool/WorkItemsQueue.cs b/Networking/SmartThreadPool/WorkItemsQueue.cs new file mode 100644 index 0000000..e0bc916 --- /dev/null +++ b/Networking/SmartThreadPool/WorkItemsQueue.cs @@ -0,0 +1,645 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Amib.Threading.Internal +{ + #region WorkItemsQueue class + + /// <summary> + /// WorkItemsQueue class. + /// </summary> + public class WorkItemsQueue : IDisposable + { + #region Member variables + + /// <summary> + /// Waiters queue (implemented as stack). + /// </summary> + private readonly WaiterEntry _headWaiterEntry = new WaiterEntry(); + + /// <summary> + /// Waiters count + /// </summary> + private int _waitersCount = 0; + + /// <summary> + /// Work items queue + /// </summary> + private readonly PriorityQueue _workItems = new PriorityQueue(); + + /// <summary> + /// Indicate that work items are allowed to be queued + /// </summary> + private bool _isWorkItemsQueueActive = true; + + +#if (WINDOWS_PHONE) + private static readonly Dictionary<int, WaiterEntry> _waiterEntries = new Dictionary<int, WaiterEntry>(); +#elif (_WINDOWS_CE) + private static LocalDataStoreSlot _waiterEntrySlot = Thread.AllocateDataSlot(); +#else + + [ThreadStatic] + private static WaiterEntry _waiterEntry; +#endif + + + /// <summary> + /// Each thread in the thread pool keeps its own waiter entry. + /// </summary> + private static WaiterEntry CurrentWaiterEntry + { +#if (WINDOWS_PHONE) + get + { + lock (_waiterEntries) + { + WaiterEntry waiterEntry; + if (_waiterEntries.TryGetValue(Thread.CurrentThread.ManagedThreadId, out waiterEntry)) + { + return waiterEntry; + } + } + return null; + } + set + { + lock (_waiterEntries) + { + _waiterEntries[Thread.CurrentThread.ManagedThreadId] = value; + } + } +#elif (_WINDOWS_CE) + get + { + return Thread.GetData(_waiterEntrySlot) as WaiterEntry; + } + set + { + Thread.SetData(_waiterEntrySlot, value); + } +#else + get + { + return _waiterEntry; + } + set + { + _waiterEntry = value; + } +#endif + } + + /// <summary> + /// A flag that indicates if the WorkItemsQueue has been disposed. + /// </summary> + private bool _isDisposed = false; + + #endregion + + #region Public properties + + /// <summary> + /// Returns the current number of work items in the queue + /// </summary> + public int Count + { + get + { + return _workItems.Count; + } + } + + /// <summary> + /// Returns the current number of waiters + /// </summary> + public int WaitersCount + { + get + { + return _waitersCount; + } + } + + + #endregion + + #region Public methods + + /// <summary> + /// Enqueue a work item to the queue. + /// </summary> + public bool EnqueueWorkItem(WorkItem workItem) + { + // A work item cannot be null, since null is used in the + // WaitForWorkItem() method to indicate timeout or cancel + if (null == workItem) + { + throw new ArgumentNullException("workItem" , "workItem cannot be null"); + } + + bool enqueue = true; + + // First check if there is a waiter waiting for work item. During + // the check, timed out waiters are ignored. If there is no + // waiter then the work item is queued. + lock(this) + { + ValidateNotDisposed(); + + if (!_isWorkItemsQueueActive) + { + return false; + } + + while(_waitersCount > 0) + { + // Dequeue a waiter. + WaiterEntry waiterEntry = PopWaiter(); + + // Signal the waiter. On success break the loop + if (waiterEntry.Signal(workItem)) + { + enqueue = false; + break; + } + } + + if (enqueue) + { + // Enqueue the work item + _workItems.Enqueue(workItem); + } + } + return true; + } + + + /// <summary> + /// Waits for a work item or exits on timeout or cancel + /// </summary> + /// <param name="millisecondsTimeout">Timeout in milliseconds</param> + /// <param name="cancelEvent">Cancel wait handle</param> + /// <returns>Returns true if the resource was granted</returns> + public WorkItem DequeueWorkItem( + int millisecondsTimeout, + WaitHandle cancelEvent) + { + // This method cause the caller to wait for a work item. + // If there is at least one waiting work item then the + // method returns immidiately with it. + // + // If there are no waiting work items then the caller + // is queued between other waiters for a work item to arrive. + // + // If a work item didn't come within millisecondsTimeout or + // the user canceled the wait by signaling the cancelEvent + // then the method returns null to indicate that the caller + // didn't get a work item. + + WaiterEntry waiterEntry; + WorkItem workItem = null; + lock (this) + { + ValidateNotDisposed(); + + // If there are waiting work items then take one and return. + if (_workItems.Count > 0) + { + workItem = _workItems.Dequeue() as WorkItem; + return workItem; + } + + // No waiting work items ... + + // Get the waiter entry for the waiters queue + waiterEntry = GetThreadWaiterEntry(); + + // Put the waiter with the other waiters + PushWaiter(waiterEntry); + } + + // Prepare array of wait handle for the WaitHandle.WaitAny() + WaitHandle [] waitHandles = new WaitHandle[] { + waiterEntry.WaitHandle, + cancelEvent }; + + // Wait for an available resource, cancel event, or timeout. + + // During the wait we are supposes to exit the synchronization + // domain. (Placing true as the third argument of the WaitAny()) + // It just doesn't work, I don't know why, so I have two lock(this) + // statments instead of one. + + int index = STPEventWaitHandle.WaitAny( + waitHandles, + millisecondsTimeout, + true); + + lock(this) + { + // success is true if it got a work item. + bool success = (0 == index); + + // The timeout variable is used only for readability. + // (We treat cancel as timeout) + bool timeout = !success; + + // On timeout update the waiterEntry that it is timed out + if (timeout) + { + // The Timeout() fails if the waiter has already been signaled + timeout = waiterEntry.Timeout(); + + // On timeout remove the waiter from the queue. + // Note that the complexity is O(1). + if(timeout) + { + RemoveWaiter(waiterEntry, false); + } + + // Again readability + success = !timeout; + } + + // On success return the work item + if (success) + { + workItem = waiterEntry.WorkItem; + + if (null == workItem) + { + workItem = _workItems.Dequeue() as WorkItem; + } + } + } + // On failure return null. + return workItem; + } + + /// <summary> + /// Cleanup the work items queue, hence no more work + /// items are allowed to be queue + /// </summary> + private void Cleanup() + { + lock(this) + { + // Deactivate only once + if (!_isWorkItemsQueueActive) + { + return; + } + + // Don't queue more work items + _isWorkItemsQueueActive = false; + + foreach(WorkItem workItem in _workItems) + { + workItem.DisposeOfState(); + } + + // Clear the work items that are already queued + _workItems.Clear(); + + // Note: + // I don't iterate over the queue and dispose of work items's states, + // since if a work item has a state object that is still in use in the + // application then I must not dispose it. + + // Tell the waiters that they were timed out. + // It won't signal them to exit, but to ignore their + // next work item. + while(_waitersCount > 0) + { + WaiterEntry waiterEntry = PopWaiter(); + waiterEntry.Timeout(); + } + } + } + + public object[] GetStates() + { + lock (this) + { + object[] states = new object[_workItems.Count]; + int i = 0; + foreach (WorkItem workItem in _workItems) + { + states[i] = workItem.GetWorkItemResult().State; + ++i; + } + return states; + } + } + + #endregion + + #region Private methods + + /// <summary> + /// Returns the WaiterEntry of the current thread + /// </summary> + /// <returns></returns> + /// In order to avoid creation and destuction of WaiterEntry + /// objects each thread has its own WaiterEntry object. + private static WaiterEntry GetThreadWaiterEntry() + { + if (null == CurrentWaiterEntry) + { + CurrentWaiterEntry = new WaiterEntry(); + } + CurrentWaiterEntry.Reset(); + return CurrentWaiterEntry; + } + + #region Waiters stack methods + + /// <summary> + /// Push a new waiter into the waiter's stack + /// </summary> + /// <param name="newWaiterEntry">A waiter to put in the stack</param> + public void PushWaiter(WaiterEntry newWaiterEntry) + { + // Remove the waiter if it is already in the stack and + // update waiter's count as needed + RemoveWaiter(newWaiterEntry, false); + + // If the stack is empty then newWaiterEntry is the new head of the stack + if (null == _headWaiterEntry._nextWaiterEntry) + { + _headWaiterEntry._nextWaiterEntry = newWaiterEntry; + newWaiterEntry._prevWaiterEntry = _headWaiterEntry; + + } + // If the stack is not empty then put newWaiterEntry as the new head + // of the stack. + else + { + // Save the old first waiter entry + WaiterEntry oldFirstWaiterEntry = _headWaiterEntry._nextWaiterEntry; + + // Update the links + _headWaiterEntry._nextWaiterEntry = newWaiterEntry; + newWaiterEntry._nextWaiterEntry = oldFirstWaiterEntry; + newWaiterEntry._prevWaiterEntry = _headWaiterEntry; + oldFirstWaiterEntry._prevWaiterEntry = newWaiterEntry; + } + + // Increment the number of waiters + ++_waitersCount; + } + + /// <summary> + /// Pop a waiter from the waiter's stack + /// </summary> + /// <returns>Returns the first waiter in the stack</returns> + private WaiterEntry PopWaiter() + { + // Store the current stack head + WaiterEntry oldFirstWaiterEntry = _headWaiterEntry._nextWaiterEntry; + + // Store the new stack head + WaiterEntry newHeadWaiterEntry = oldFirstWaiterEntry._nextWaiterEntry; + + // Update the old stack head list links and decrement the number + // waiters. + RemoveWaiter(oldFirstWaiterEntry, true); + + // Update the new stack head + _headWaiterEntry._nextWaiterEntry = newHeadWaiterEntry; + if (null != newHeadWaiterEntry) + { + newHeadWaiterEntry._prevWaiterEntry = _headWaiterEntry; + } + + // Return the old stack head + return oldFirstWaiterEntry; + } + + /// <summary> + /// Remove a waiter from the stack + /// </summary> + /// <param name="waiterEntry">A waiter entry to remove</param> + /// <param name="popDecrement">If true the waiter count is always decremented</param> + private void RemoveWaiter(WaiterEntry waiterEntry, bool popDecrement) + { + // Store the prev entry in the list + WaiterEntry prevWaiterEntry = waiterEntry._prevWaiterEntry; + + // Store the next entry in the list + WaiterEntry nextWaiterEntry = waiterEntry._nextWaiterEntry; + + // A flag to indicate if we need to decrement the waiters count. + // If we got here from PopWaiter then we must decrement. + // If we got here from PushWaiter then we decrement only if + // the waiter was already in the stack. + bool decrementCounter = popDecrement; + + // Null the waiter's entry links + waiterEntry._prevWaiterEntry = null; + waiterEntry._nextWaiterEntry = null; + + // If the waiter entry had a prev link then update it. + // It also means that the waiter is already in the list and we + // need to decrement the waiters count. + if (null != prevWaiterEntry) + { + prevWaiterEntry._nextWaiterEntry = nextWaiterEntry; + decrementCounter = true; + } + + // If the waiter entry had a next link then update it. + // It also means that the waiter is already in the list and we + // need to decrement the waiters count. + if (null != nextWaiterEntry) + { + nextWaiterEntry._prevWaiterEntry = prevWaiterEntry; + decrementCounter = true; + } + + // Decrement the waiters count if needed + if (decrementCounter) + { + --_waitersCount; + } + } + + #endregion + + #endregion + + #region WaiterEntry class + + // A waiter entry in the _waiters queue. + public sealed class WaiterEntry : IDisposable + { + #region Member variables + + /// <summary> + /// Event to signal the waiter that it got the work item. + /// </summary> + //private AutoResetEvent _waitHandle = new AutoResetEvent(false); + private AutoResetEvent _waitHandle = EventWaitHandleFactory.CreateAutoResetEvent(); + + /// <summary> + /// Flag to know if this waiter already quited from the queue + /// because of a timeout. + /// </summary> + private bool _isTimedout = false; + + /// <summary> + /// Flag to know if the waiter was signaled and got a work item. + /// </summary> + private bool _isSignaled = false; + + /// <summary> + /// A work item that passed directly to the waiter withou going + /// through the queue + /// </summary> + private WorkItem _workItem = null; + + private bool _isDisposed = false; + + // Linked list members + internal WaiterEntry _nextWaiterEntry = null; + internal WaiterEntry _prevWaiterEntry = null; + + #endregion + + #region Construction + + public WaiterEntry() + { + Reset(); + } + + #endregion + + #region Public methods + + public WaitHandle WaitHandle + { + get { return _waitHandle; } + } + + public WorkItem WorkItem + { + get + { + return _workItem; + } + } + + /// <summary> + /// Signal the waiter that it got a work item. + /// </summary> + /// <returns>Return true on success</returns> + /// The method fails if Timeout() preceded its call + public bool Signal(WorkItem workItem) + { + lock(this) + { + if (!_isTimedout) + { + _workItem = workItem; + _isSignaled = true; + _waitHandle.Set(); + return true; + } + } + return false; + } + + /// <summary> + /// Mark the wait entry that it has been timed out + /// </summary> + /// <returns>Return true on success</returns> + /// The method fails if Signal() preceded its call + public bool Timeout() + { + lock(this) + { + // Time out can happen only if the waiter wasn't marked as + // signaled + if (!_isSignaled) + { + // We don't remove the waiter from the queue, the DequeueWorkItem + // method skips _waiters that were timed out. + _isTimedout = true; + return true; + } + } + return false; + } + + /// <summary> + /// Reset the wait entry so it can be used again + /// </summary> + public void Reset() + { + _workItem = null; + _isTimedout = false; + _isSignaled = false; + _waitHandle.Reset(); + } + + /// <summary> + /// Free resources + /// </summary> + public void Close() + { + if (null != _waitHandle) + { + _waitHandle.Close(); + _waitHandle = null; + } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + lock (this) + { + if (!_isDisposed) + { + Close(); + } + _isDisposed = true; + } + } + + #endregion + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + if (!_isDisposed) + { + Cleanup(); + } + _isDisposed = true; + } + + private void ValidateNotDisposed() + { + if(_isDisposed) + { + throw new ObjectDisposedException(GetType().ToString(), "The SmartThreadPool has been shutdown"); + } + } + + #endregion + } + + #endregion +} + diff --git a/Networking/packages.config b/Networking/packages.config index d8fbadb..3a091df 100644 --- a/Networking/packages.config +++ b/Networking/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="PTConverter.Plugin" version="1.0.2" targetFramework="net472" /> + <package id="PTConverter.Plugin" version="1.0.3" targetFramework="net472" /> </packages> \ No newline at end of file diff --git a/WpfApp1/App.config b/WpfApp1/App.config new file mode 100644 index 0000000..193aecc --- /dev/null +++ b/WpfApp1/App.config @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8" ?> +<configuration> + <startup> + <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /> + </startup> +</configuration> \ No newline at end of file diff --git a/WpfApp1/App.xaml b/WpfApp1/App.xaml new file mode 100644 index 0000000..2e70522 --- /dev/null +++ b/WpfApp1/App.xaml @@ -0,0 +1,9 @@ +<Application x:Class="WpfApp1.App" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:WpfApp1" + StartupUri="MainWindow.xaml"> + <Application.Resources> + + </Application.Resources> +</Application> diff --git a/WpfApp1/App.xaml.cs b/WpfApp1/App.xaml.cs new file mode 100644 index 0000000..9cacf07 --- /dev/null +++ b/WpfApp1/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace WpfApp1 +{ + /// <summary> + /// Interaktionslogik für "App.xaml" + /// </summary> + public partial class App : Application + { + } +} diff --git a/WpfApp1/MainWindow.xaml b/WpfApp1/MainWindow.xaml new file mode 100644 index 0000000..d845a0e --- /dev/null +++ b/WpfApp1/MainWindow.xaml @@ -0,0 +1,15 @@ +<Window + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:local="clr-namespace:WpfApp1" + xmlns:Pages="clr-namespace:Networking.Pages;assembly=Networking" x:Class="WpfApp1.MainWindow" + mc:Ignorable="d" + Title="MainWindow" Height="450" Width="800"> + <Grid> + + <Pages:IPScanner HorizontalAlignment="Left" Height="336" Margin="0,55,0,0" VerticalAlignment="Top" Width="782"/> + + </Grid> +</Window> diff --git a/WpfApp1/MainWindow.xaml.cs b/WpfApp1/MainWindow.xaml.cs new file mode 100644 index 0000000..4109b12 --- /dev/null +++ b/WpfApp1/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace WpfApp1 +{ + /// <summary> + /// Interaktionslogik für MainWindow.xaml + /// </summary> + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/WpfApp1/Properties/AssemblyInfo.cs b/WpfApp1/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5e66bf5 --- /dev/null +++ b/WpfApp1/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("WpfApp1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WpfApp1")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +//Um mit dem Erstellen lokalisierbarer Anwendungen zu beginnen, legen Sie +//<UICulture>ImCodeVerwendeteKultur</UICulture> in der .csproj-Datei +//in einer <PropertyGroup> fest. Wenn Sie in den Quelldateien beispielsweise Deutsch +//(Deutschland) verwenden, legen Sie <UICulture> auf \"de-DE\" fest. Heben Sie dann die Auskommentierung +//des nachstehenden NeutralResourceLanguage-Attributs auf. Aktualisieren Sie "en-US" in der nachstehenden Zeile, +//sodass es mit der UICulture-Einstellung in der Projektdatei übereinstimmt. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //Speicherort der designspezifischen Ressourcenwörterbücher + //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, + // oder in den Anwendungsressourcen-Wörterbüchern nicht gefunden werden kann.) + ResourceDictionaryLocation.SourceAssembly //Speicherort des generischen Ressourcenwörterbuchs + //(wird verwendet, wenn eine Ressource auf der Seite nicht gefunden wird, + // designspezifischen Ressourcenwörterbuch nicht gefunden werden kann.) +)] + + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WpfApp1/Properties/Resources.Designer.cs b/WpfApp1/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0cc49c3 --- /dev/null +++ b/WpfApp1/Properties/Resources.Designer.cs @@ -0,0 +1,70 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion: 4.0.30319.42000 +// +// Änderungen an dieser Datei können fehlerhaftes Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// </auto-generated> +//------------------------------------------------------------------------------ + + +namespace WpfApp1.Properties +{ + /// <summary> + /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// </summary> + // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse + // über ein Tool wie ResGen oder Visual Studio automatisch generiert. + // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen + // mit der Option /str erneut aus, oder erstellen Sie Ihr VS-Projekt neu. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// <summary> + /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApp1.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// <summary> + /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle + /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden. + /// </summary> + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/WpfApp1/Properties/Resources.resx b/WpfApp1/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/WpfApp1/Properties/Resources.resx @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> +</root> \ No newline at end of file diff --git a/WpfApp1/Properties/Settings.Designer.cs b/WpfApp1/Properties/Settings.Designer.cs new file mode 100644 index 0000000..fb939d8 --- /dev/null +++ b/WpfApp1/Properties/Settings.Designer.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + + +namespace WpfApp1.Properties +{ + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/WpfApp1/Properties/Settings.settings b/WpfApp1/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/WpfApp1/Properties/Settings.settings @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)"> + <Profiles> + <Profile Name="(Default)" /> + </Profiles> + <Settings /> +</SettingsFile> \ No newline at end of file diff --git a/WpfApp1/WpfApp1.csproj b/WpfApp1/WpfApp1.csproj new file mode 100644 index 0000000..4c2cbc6 --- /dev/null +++ b/WpfApp1/WpfApp1.csproj @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{89252909-F8E2-4BDB-8EA9-4DA1A329545C}</ProjectGuid> + <OutputType>WinExe</OutputType> + <RootNamespace>WpfApp1</RootNamespace> + <AssemblyName>WpfApp1</AssemblyName> + <TargetFrameworkVersion>v4.8</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> + <WarningLevel>4</WarningLevel> + <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> + <Deterministic>true</Deterministic> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="PTConverter.Plugin, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\PTConverter.Plugin.1.0.3\lib\net472\PTConverter.Plugin.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Data" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Windows.Forms" /> + <Reference Include="System.Xml" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xaml"> + <RequiredTargetFramework>4.0</RequiredTargetFramework> + </Reference> + <Reference Include="UIAutomationProvider" /> + <Reference Include="WindowsBase" /> + <Reference Include="PresentationCore" /> + <Reference Include="PresentationFramework" /> + <Reference Include="WindowsFormsIntegration" /> + </ItemGroup> + <ItemGroup> + <ApplicationDefinition Include="App.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </ApplicationDefinition> + <Page Include="MainWindow.xaml"> + <Generator>MSBuild:Compile</Generator> + <SubType>Designer</SubType> + </Page> + <Compile Include="App.xaml.cs"> + <DependentUpon>App.xaml</DependentUpon> + <SubType>Code</SubType> + </Compile> + <Compile Include="MainWindow.xaml.cs"> + <DependentUpon>MainWindow.xaml</DependentUpon> + <SubType>Code</SubType> + </Compile> + </ItemGroup> + <ItemGroup> + <Compile Include="Properties\AssemblyInfo.cs"> + <SubType>Code</SubType> + </Compile> + <Compile Include="Properties\Resources.Designer.cs"> + <AutoGen>True</AutoGen> + <DesignTime>True</DesignTime> + <DependentUpon>Resources.resx</DependentUpon> + </Compile> + <Compile Include="Properties\Settings.Designer.cs"> + <AutoGen>True</AutoGen> + <DependentUpon>Settings.settings</DependentUpon> + <DesignTimeSharedInput>True</DesignTimeSharedInput> + </Compile> + <EmbeddedResource Include="Properties\Resources.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>Resources.Designer.cs</LastGenOutput> + </EmbeddedResource> + <None Include="packages.config" /> + <None Include="Properties\Settings.settings"> + <Generator>SettingsSingleFileGenerator</Generator> + <LastGenOutput>Settings.Designer.cs</LastGenOutput> + </None> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Networking\Networking.csproj"> + <Project>{85d8795c-e9dc-4a59-b669-e2a8eeac7a9e}</Project> + <Name>Networking</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> +</Project> \ No newline at end of file diff --git a/WpfApp1/packages.config b/WpfApp1/packages.config new file mode 100644 index 0000000..13b8e9a --- /dev/null +++ b/WpfApp1/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="PTConverter.Plugin" version="1.0.3" targetFramework="net48" /> +</packages> \ No newline at end of file