1. 程式人生 > >解決WebService 第一次慢的問題 在wince下

解決WebService 第一次慢的問題 在wince下

正好做一個wince裝置的專案,以前也做過類似的,但是應用屬於輕量級的,

因此對於web service慢的問題沒有太多關注。這次也同樣發現了這個問題,

調式幾次連Hello, World 都很慢,簡直無法容忍。於是在網上搜索此問題的解決方法。

問題的原因很簡單,就是因為在第一次連結web service時應用程式動態編譯生成序列化程式集導致的,

有詳細的解釋,並附有PreGen.exe工具的原始碼文章上的建議是使用PreGen.exe工具直接生成序列化程式集,

在程式中直接載入,解決每次動態生成從而加快web service的速度。但是文章中

使用[System.Xml.Serialization.XmlSerializerAssemblyAttribute(CodeBase="< DLL 名")]
對生成的序列化程式集進行載入,並不適用於wince裝置,

因為dotnetCF沒有System.Xml.Serialization.XmlSerializerAssemblyAttribute屬性。

在網上查了大多,也沒有找到在wince環境下如何載入的方法。

又仔細查看了英文的文件,有一條命令引起的我的注意那就是LoadFrom
又對比了下中文文件,也是說可以使用該命令來載入。既然有說明而且在wince下可以執行,那就試下吧。
開啟Program.cs檔案,在Application.Run之前加入System.Reflection.Assembly.LoadFrom("XXXX.XmlSerializers.dll");編譯後執行,在輸出中
看到dll載入成功啦,在執行web service呼叫速度很快,完全沒有問題啦。

至此在wince下呼叫web service慢的問題完全解決

using System;

using System.Collections.Generic;
using System.Windows.Forms;

namespace TestWeb
{
    using System.Reflection;
    static class Program
    {
        /// <summary>
        /// 應用程式的主入口點。
        /// </summary>
        [MTAThread]
        static void Main()
        {
            Assembly.LoadFrom("\\debug\\TestWeb.XmlSerializers.dll");
            Application.Run(new Main());
        }
    }
}

另外給大家附上另一個生成序列化程式集的方法(僅限PC端,此方法程式自動載入序列化程式集)

PreGen.cs原始碼,簡單版

namespace PreGenNS {
    using System;
    using System.Collections;
    using System.IO;
    using System.Reflection;
    using System.Xml.Serialization;
    using System.Text;
    using System.Globalization;
    using System.Web.Services.Protocols;
    using System.Threading;
    using System.CodeDom.Compiler;
    
    public class Pregen {
        public static int Main(string[] args) {
            if (args.Length != 1) {
                Console.WriteLine("usage: ");
                Console.WriteLine("  pregen assembly");
                return 1;
            }
            Pregen pregen = new Pregen();
            return pregen.Run(args[0]);
        }

        int Run(string assemblyName) {
            
            try {
                GenerateAssembly(assemblyName);
            }
            catch (Exception e) {
                if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                    throw;
                }
                Error(e, "Error: ");
                return 1;
            }
            return 0;
        }

        void GenerateAssembly(string assemblyName) {
            Assembly assembly = LoadAssembly(assemblyName, true);
            Type[] types = assembly.GetTypes();
            ArrayList mappings = new ArrayList();
            ArrayList importedTypes = new ArrayList();
            XmlReflectionImporter importer = new XmlReflectionImporter();
            for (int i = 0; i < types.Length; i++) {
                Type type = types[i];
                if (HttpWebClientProtocol.GenerateXmlMappings(type, mappings)) {
                    importedTypes.Add(type);
                }
            }
            if (importedTypes.Count > 0) {
                Type[] serializableTypes = (Type[])importedTypes.ToArray(typeof(Type));
                XmlMapping[] allMappings = (XmlMapping[])mappings.ToArray(typeof(XmlMapping));
                
                bool gac = assembly.GlobalAssemblyCache;
                string codePath = gac ? Environment.CurrentDirectory : Path.GetDirectoryName(assembly.Location);
                string serializerName = assembly.GetName().Name + ".XmlSerializers" ;
                string location = Path.Combine(codePath, serializerName + ".dll");

                CompilerParameters parameters = new CompilerParameters();
                parameters.TempFiles = new TempFileCollection(codePath);
                parameters.GenerateInMemory = false;
                parameters.IncludeDebugInformation = false;
                parameters.TempFiles = new TempFileCollection(codePath, true);
                Assembly serializer = XmlSerializer.GenerateSerializer(serializableTypes, allMappings, parameters);
                if (serializer == null) {
                    Console.Out.WriteLine("Failed pregenerate serializer for '{0}'", assembly.Location);
                }
                else {
                    AssemblyName name = serializer.GetName();
                    Console.Out.WriteLine("Serialization Assembly Name: {0}", name.ToString());
                    Console.Out.WriteLine("Generated serialization assembly for assembly {0} --> '{1}'.", assembly.Location, location);
                }
            }
            else {
                Console.Out.WriteLine("Assembly '{0}' does not contain any serializable types.", assembly.Location);
            }
        }

        static Assembly LoadAssembly(string assemblyName, bool throwOnFail) {
            Assembly assembly = null;
            string path = Path.GetFullPath(assemblyName).ToLower(CultureInfo.InvariantCulture);
            if (File.Exists(path)) {
                assembly = Assembly.LoadFrom(path);
            }
            else {
                try {
                    assembly = Assembly.Load(assemblyName);
                }
                catch (Exception e) {
                    if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                        throw;
                    }
                    Error(e, "Error: ");
                }
                if (assembly == null) {
                    string justName = Path.GetFileNameWithoutExtension(assemblyName);
                    try {
                        assembly = Assembly.Load(justName);
                    }
                    catch (Exception e) {
                        if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                            throw;
                        }
                        Error(e, "Error: ");
                    }
                }
            }
            if (assembly == null) {
                if (throwOnFail)
                    throw new InvalidOperationException("Cannot load assembly " + assemblyName);
                return null;
            }
            return assembly;
        }

        static void Error(Exception e, string prefix) {
            Console.Error.WriteLine(prefix + e.Message);
            if (e.InnerException != null) {
                Error(e.InnerException, "  - ");
            }
        }

        static void Warning(Exception e) {
            Console.Out.WriteLine("  - " + e.Message);
            if (e.InnerException != null) {
                Warning(e.InnerException);
            }
        }
    }
}


簽名版

namespace PreGenNS 
{
	using System;
	using System.Collections;
	using System.IO;
	using System.Reflection;
	using System.Xml.Serialization;
	using System.Text;
	using System.Globalization;
	using System.Web.Services.Protocols;
	using System.Threading;
	using System.CodeDom.Compiler;
	using System.Diagnostics;
	using System.Text.RegularExpressions;

	public class Pregen 
	{
		private static bool _verbose = true;

		
		// We use this as the standard suffix on all the serializer DLLs.
		// It must match the short name of the proxy assembly class with 
		const string SerializerSuffix = ".Serializer.dll";

		
		/// Key in the app config file with path of AssemblyInfo file.
		private const string AssemblyInfoAppKey = "AssemblyInfoFile";

		//private const string CSCCmd = "csc.exe";
		// Obtain the full path for the compiler, just in case the path is not set correctly

		private readonly string CSCCmd = System.Runtime.InteropServices.RuntimeEnvironment.RuntimeDirectory() + "csc.exe";

		public static int Main(string[] args) {		
			// Are we in a recursive call?
			//TODO: could really use a single value -- use filesToDelete...
			if (AppDomain.CurrentDomain.GetData(CallingAppDomainKey) == null) {
				return RunSlave(args);
			}

			string dllName = "";
			bool invalidUsage = false;

			if(args.Length == 1)
			{
				dllName = args[0];
			}
			else if(args.Length == 2)
			{
				if(!(args[0] == "/Q" || args[0] == "/q"))
				{
					invalidUsage = true;
				}
				else
				{
					dllName = args[1];
					_verbose = false;
				}
			}
			else 
			{
				invalidUsage = true;
			}

			if (invalidUsage)
			{
				Console.WriteLine("usage: ");
				Console.WriteLine(" pregen [/Q] assembly");
				return 1;
			}
			

			// Update Private path setting in current application domain
			if (updatePrivatePath() != 0)
			{
				return 1;
			}
			
			Pregen pregen = new Pregen();
			int rc = pregen.Run(dllName);
			//Console.Read();
			return rc;
		}


		// Reads private path settings from config file and updates appdomain.  This permits configurable probing that is needed for preserialization.
		static int updatePrivatePath()
		{
			string defaultPrivatePath = System.Configuration.ConfigurationSettings.AppSettings["PregenDefaultPrivatePath"];
			string dynamicPrivatePath = System.Configuration.ConfigurationSettings.AppSettings["PregenDynamicPrivatePath"];
			string env_PREGEN_VALUES = Environment.GetEnvironmentVariable("PREGEN_VALUES");
			string [] replacementBlocks, temp;

			if (_verbose)
				Console.WriteLine("Read PREGEN_VALUES Env Variable, Value = " + env_PREGEN_VALUES);

			//process the dynamic path if the environment variable PREGEN_VALUES is present 
			if (env_PREGEN_VALUES == null || env_PREGEN_VALUES == "")
			{
				if (defaultPrivatePath != null && defaultPrivatePath != "")
				{
					AppDomain.CurrentDomain.AppendPrivatePath(defaultPrivatePath);

					if (_verbose)
						Console.WriteLine("Appended private path with: " + defaultPrivatePath);
				}
			}
			else
			{
				if (dynamicPrivatePath != null && dynamicPrivatePath != "")
				{
					//do substitutions in dynamic path
					replacementBlocks = env_PREGEN_VALUES.ToUpper().Split(";".ToCharArray());
					dynamicPrivatePath = dynamicPrivatePath.ToUpper();

					for(int i = 0; i < replacementBlocks.Length; i++)
					{
						temp = replacementBlocks[i].Split("=".ToCharArray());
						if(temp.Length != 2)
						{
							Console.Error.WriteLine("Invalid Environment Variable format - PREGEN_VALUES");
							return 1;
						}

						dynamicPrivatePath = dynamicPrivatePath.Replace(temp[0],temp[1]);
					}
					
					AppDomain.CurrentDomain.AppendPrivatePath(dynamicPrivatePath);

					if (_verbose )
						Console.WriteLine("Appended private path with: " + dynamicPrivatePath);
				}
			}
			return 0;
		}
		
		int Run(string assemblyName) 
		{

			try 
			{
				GenerateAssembly(assemblyName);
			}
			catch (Exception e) 
			{
				if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) 
				{
					throw;
				}
				Error(e, "Error processing " + assemblyName + ": ");
				return 1;
			}
			return 0;
		}



		// Generates the serializer assembly for the proxy assembly specified
		void GenerateAssembly(string assemblyName) 
		{
			Assembly assembly = LoadAssembly(assemblyName, true);
			Type[] types = assembly.GetTypes();
			ArrayList mappings = new ArrayList();
			ArrayList importedTypes = new ArrayList();
			XmlReflectionImporter importer = new XmlReflectionImporter();

			
			//Obtain the imported serializable types
			for (int i = 0; i < types.Length; i++) 
			{
				Type type = types[i];
				if (HttpWebClientProtocol.GenerateXmlMappings(type, mappings)) 
				{
					importedTypes.Add(type);
				}
			}
			if (importedTypes.Count <= 0) {
				Console.Out.WriteLine("Assembly '{0}' does not contain any serializable types.", assembly.Location);
				return;
			}
 
			{
				Type[] serializableTypes = (Type[])importedTypes.ToArray(typeof(Type));
				XmlMapping[] allMappings = (XmlMapping[])mappings.ToArray(typeof(XmlMapping));

				bool wasError = false;
				bool gac = assembly.GlobalAssemblyCache;
				string codePath = gac ? Environment.CurrentDirectory : Path.GetDirectoryName(assembly.Location);

				//adjust compiler params
				CompilerParameters parameters = new CompilerParameters();
				parameters.GenerateInMemory = false;
				parameters.IncludeDebugInformation = false;
				parameters.TempFiles = new TempFileCollection(codePath, true);
				
				//generate the serializer
				Assembly serializer = XmlSerializer.GenerateSerializer(serializableTypes, allMappings, parameters);
				if (serializer == null) {
					Console.Out.WriteLine("Failed pregenerate serializer for '{0}'", assembly.Location);
					wasError = true;
				}
				else 
				{
					serializer = null;
				}

				// Determine whether there is an assemblyInfoFile in the config file.
				string assemblyInfoFile = System.Configuration.ConfigurationSettings.AppSettings[AssemblyInfoAppKey];
				if (assemblyInfoFile != null) { 
					if (! File.Exists(assemblyInfoFile)) {
						Console.WriteLine("ERROR: AssemblyInfo file: {0} does not exist.", assemblyInfoFile);
						wasError = true;
					}
				}

				if (!wasError) {
					// Recompile the Serializer, same options, except to include the assemblyInfo file and
					// adjust the output name.

					// We have to find 
					//		1. a .cs file (the serializer source)
					//		2. a .cmdline file (the compiler options used)
					// among the temp files from the first compile.
					
					string csFile		= null;
					string cmdlineFile	= null;

					foreach (string curFile in parameters.TempFiles ) {
						string fileNameLC = curFile.ToLower();
						if (fileNameLC.EndsWith(".cs") ) {
							csFile = curFile;
						}
						else if (fileNameLC.EndsWith(".cmdline")) {
							cmdlineFile = curFile;
						}
						//Do not care about the other files...
					}
					if (csFile == null || cmdlineFile == null) {
						Console.WriteLine("Error: needed to rebuild, but cannot find either .cs or .cmdline file\n");
						DeleteTempFiles(parameters);
						return;
					}

					// So now we have found the file and the cmdline args.  We only need run the compiled application after 
					// adjusting the parameters to include the AssemblyInfo file and to change the output.
					
					// Typical calling options to csc for this sequence are:
					//		csc /noconfig @xxx.cmdline
					// we'll change this to expand the contents of the cmdline file

					// build the right name for the target serializer.
					//TODO: we should be able to read the attribute from the proxy DLL and match our output
					// to that name.
					string serializerName = Path.GetDirectoryName(assembly.Location) + @"\" 
											+ assembly.GetName().Name + SerializerSuffix;

					string cmdLine = AdjustCmdLine(cmdlineFile, assemblyInfoFile, serializerName);

					ProcessStartInfo ps = new ProcessStartInfo(CSCCmd, cmdLine);
					ps.WindowStyle = ProcessWindowStyle.Hidden;
					Process p = Process.Start(ps);
					p.WaitForExit();
					int rc = p.ExitCode;
					if (rc > 0) {
						//TODO: put useful handling here...
						Console.WriteLine("ERROR: Compiler problem, rc = {0}", rc);
						wasError = true;
					}

					//TODO: Cannot ditch temp assembly because the assembly is now loaded.
					DeleteTempFiles(parameters);

					if (!wasError) {
						Console.Out.WriteLine("Generated Serialization Assembly Name: {0}", serializerName);
					}
					Console.Out.WriteLine("Done");
				}
			}
		}


		// Delete temporary files from a CompilerParameters list.
		private void DeleteTempFiles(CompilerParameters cp) {
			ArrayList unDeletedFiles = new ArrayList(10);
			foreach(string fileName in cp.TempFiles) {
				try {
					File.Delete(fileName);
				}
				catch(Exception) {
					unDeletedFiles.Add(fileName);
					//Console.WriteLine("Warning: Unable to delete temp file: {0}, exception={1}(\"{2}\")",
					//	Path.GetFileName(fileName), e.GetType().FullName, e.Message);
				}
			}
			if (unDeletedFiles.Count > 0) {
				// put the list into the calling appDomain's environment for later deletion
				string[] files = new string[unDeletedFiles.Count];
				unDeletedFiles.CopyTo(files);

				//TODO: should really be concatenating to any existing value -- maybe leave as an ArrayList?
				AppDomain.CurrentDomain.SetData(FilesToDeleteKey, files);
			}
		}


		/// Rebuild a commandline for csc, adding assemblyInfoFile to the end of the line and adjusting the 
		/// output file name as specified.
		private string AdjustCmdLine(string cmdlineFile, string assemblyInfoFile, string outputFile) {	
			// Obtain the text from the @ response file that is used by the Framework Serialization builder
			StreamReader file = File.OpenText(cmdlineFile);
			string cmdLine = file.ReadToEnd();
			file.Close();

			// add the assemblyInfo file at the end of the command if it was specified
			if (assemblyInfoFile != null)
				cmdLine = String.Format(@"/noconfig {0} ""{1}""", cmdLine, assemblyInfoFile);

			// replace the /OUT option with our value.
			Regex re = new Regex(@"/OUT:""[^""]+.", RegexOptions.IgnoreCase);
			cmdLine = re.Replace(cmdLine, @"/OUT:""" + outputFile + @"""");

			return cmdLine;
		}

		static Assembly LoadAssembly(string assemblyName, bool throwOnFail) 
		{
			Assembly assembly = null;
			string path = Path.GetFullPath(assemblyName).ToLower(CultureInfo.InvariantCulture);
			if (File.Exists(path)) 
			{
				assembly = Assembly.LoadFrom(path);
			}
			else 
			{
				try 
				{
					assembly = Assembly.Load(assemblyName);
				}
				catch (Exception e) 
				{
					if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) 
					{
						throw;
					}
					Error(e, "Error: ");
				}
				if (assembly == null) 
				{
					string justName = Path.GetFileNameWithoutExtension(assemblyName);
					try 
					{
						assembly = Assembly.Load(justName);
					}
					catch (Exception e) 
					{
						if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) 
						{
							throw;
						}
						Error(e, "Error: ");
					}
				}
			}
			if (assembly == null) 
			{
				if (throwOnFail)
					throw new InvalidOperationException("Cannot load assembly " + assemblyName);
				return null;
			}
			return assembly;
		}

		static void Error(Exception e, string prefix) 
		{
			Console.Error.WriteLine(prefix + e.Message);
			if (e.InnerException != null) 
			{
				Error(e.InnerException, " - ");
			}
		}

		static void Warning(Exception e) 
		{
			Console.Out.WriteLine(" - " + e.Message);
			if (e.InnerException != null) 
			{
				Warning(e.InnerException);
			}
		}

		private static AppDomain DuplicateAppDomain(AppDomain template, string newName) {
			AppDomain res = AppDomain.CreateDomain(newName, template.Evidence, template.SetupInformation);
			return res;
		}

		// keys in AppDomain properties
		private const string CallingAppDomainKey	= "__Calling_AppDomain__";
		private const string FilesToDeleteKey		= "__Files_To_Delete__";

		// Called from Main to set up and run the second copy of the program.
		// args: command-line arguments for second execution
		private static int RunSlave(string[] args) {

			// Start a copy of this program in another application domain
			AppDomain ad = DuplicateAppDomain(AppDomain.CurrentDomain, "serializerAD");
			Assembly ca = Assembly.GetExecutingAssembly();

			// set a marker so target domain knows that it is the subordinate.
			ad.SetData(CallingAppDomainKey, AppDomain.CurrentDomain.FriendlyName);
			ad.SetData(FilesToDeleteKey, new string[0]);

			int rc = ad.ExecuteAssembly(ca.Location, ca.Evidence, args);

			// Now delete any files
			string[] fileList = (string[])ad.GetData(FilesToDeleteKey);

			AppDomain.Unload(ad);

			if (fileList != null) {
				foreach(string fileName in fileList) {
					try {
						File.Delete(fileName);
					}
					catch(Exception e) {
						Console.WriteLine("Warning: Unable to delete temp file: {0}, exception={1}(\"{2}\")",
							Path.GetFileName(fileName), e.GetType().FullName, e.Message);
					}
				}
			}
			return rc;  
		}

	}
}