解決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;
}
}
}