Usar un origen http en un BitmapImage ralentiza tu aplicación WPF

Quiero contar una experiencia que me ha ocurrido recientemente. Como ya comente en otra ocasión estoy terminando el Infotouch, pues os quiero contar un problema que he tenido de rendimiento.


Esta aplicación hace un uso intensivo de imágenes, las que la mayoría están online en un servidor y suelen cambiar cada día.


En WPF hay un control Image (System.Windows.Controls.Image) que utiliza como origen una clase BitmapImage (System.Windows.Media.Imaging.BitmapImage), pues bien en esta última clase en el constructor puedes pasarle por parametro una Uri, con la url del recurso a establecer. Pues bien es aquí donde empieza el problema, en algunos casos suelo utilizar aproximadamente 300 instancias diferentes de BitmapImage y todas las Uri son con http, cuando ejecuto la aplicación empiezo a ver que el rendimiento baja cuando tiene que cargar todas esas imagenes, y no estoy hablando de que tarde en descargarlas ni en dibujarlas, eso lo espero, sino que trabajando en local incluro con url de localhost la aplicación tarda mucho en cargarse y se lazan pues unas 800 excepciones en mi codigo.


Empiezo a investigar, depuro con el Visual Studio y en la ventana de salida encuentro esto:



   1: A first chance exception of type ‘System.Deployment.Application.InvalidDeploymentException’ occurred in System.Deployment.dll
   2: A first chance exception of type ‘System.Deployment.Application.DeploymentException’ occurred in System.Deployment.dll
   3: A first chance exception of type ‘System.Deployment.Application.InvalidDeploymentException’ occurred in System.Deployment.dll
   4: A first chance exception of type ‘System.Deployment.Application.DeploymentException’ occurred in System.Deployment.dll
   5: A first chance exception of type ‘System.Deployment.Application.InvalidDeploymentException’ occurred in System.Deployment.dll
   6: A first chance exception of type ‘System.Deployment.Application.DeploymentException’ occurred in System.Deployment.dll
   7: A first chance exception of type ‘System.Deployment.Application.InvalidDeploymentException’ occurred in System.Deployment.dll
   8: A first chance exception of type ‘System.Deployment.Application.DeploymentException’ occurred in System.Deployment.dll
   9: A first chance exception of type ‘System.Deployment.Application.InvalidDeploymentException’ occurred in System.Deployment.dll
  10: A first chance exception of type ‘System.Deployment.Application.DeploymentException’ occurred in System.Deployment.dll
  11: A first chance exception of type ‘System.Deployment.Application.InvalidDeploymentException’ occurred in System.Deployment.dll

Esas excepciones que se lanzan son causadas porque se intenta acceder a la propiedad System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed o a System.Deployment.Application.ApplicationDeployment.CurrentDeployment y en la implementación de esas dos propiedades se intenta acceder a la información de despliegue con ClicOnce.


Pues bien sabiendo esta información uno puede pensar, estas programando con clicOnce y está haciendo algo más, pues resulta que clicOnce no se utiliza en el proyecto, incluso el ensamblado System.Deployment no está referenciado con lo cual eso tiene que estar en código de Microsoft.


Vamos a investigar.


Para poder depurar este código lo único que sabemos es que se lanza una excepción pero no se propaga sino que está dentro de un catch, ¿Como podemos hacer que Visual Studio se pare en ese trozo de código?, la pregunta es dificil, pero se puede hacer. Se puede hacer algo parecido, ya que no disponemos del código fuente del .net framewokr (si la tenemos, pero supongamos que no) tenemos que usar el WinDbg. Lo abrimos y cargamos la dll de depuración de código administrador en Windbg (sos.dll) a través del comando .loadby sos mscorwks una vez que tenmos eso tenemos que decirle al Windbg que se pare cuando una excepción del tipo System.Deployment.Application.InvalidDeploymentException ocurra, pues bien eso se puede hacer con el comando, !soe -create System.Deployment.Application.InvalidDeploymentException 1 (soe de StopOnException), así que una vez que el Windbg se para analizamos la excepción con !analyze -v y nos encontramos con esto:



   1:  
   2: FAULTING_IP: 
   3: KERNEL32!RaiseException+58
   4: 766742eb c9              leave
   5:  
   6: EXCEPTION_RECORD:  ffffffff — (.exr 0xffffffffffffffff)
   7: ExceptionAddress: 766742eb (KERNEL32!RaiseException+0x00000058)
   8:    ExceptionCode: e0434f4d (CLR exception)
   9:   ExceptionFlags: 00000001
  10: NumberParameters: 1
  11:    Parameter[0]: 80131501
  12:  
  13: FAULTING_THREAD:  00000f78
  14:  
  15: DEFAULT_BUCKET_ID:  CLR_EXCEPTION
  16:  
  17: PROCESS_NAME:  infotouch2.exe
  18:  
  19: ERROR_CODE: (NTSTATUS) 0xe0434f4d – <Unable to get error code text>
  20:  
  21: NTGLOBALFLAG:  70
  22:  
  23: APPLICATION_VERIFIER_FLAGS:  0
  24:  
  25: MANAGED_STACK: 
  26: (TransitionMU)
  27: 011EDDF4 6A325FC8 System_Deployment_ni!System.Deployment.Application.ApplicationDeployment.get_CurrentDeployment()+0xf4
  28: 011EDE28 6A326056 System_Deployment_ni!System.Deployment.Application.ApplicationDeployment.get_IsNetworkDeployed()+0x1a
  29: 011EDE54 53943BD2 PresentationCore_ni!MS.Internal.AppModel.SiteOfOriginContainer.get_SiteOfOriginForBrowserApplications()+0x62
  30: 011EDE60 539E289D PresentationCore_ni!MS.Internal.PresentationCore.SecurityHelper.ExtractUriForClickOnceDeployedApp()+0x15
  31: 011EDE64 539E28DC PresentationCore_ni!MS.Internal.PresentationCore.SecurityHelper.BlockCrossDomainForHttpsApps(System.Uri)+0x34
  32: 011EDE7C 53B6908A PresentationCore_ni!System.Windows.Media.Imaging.BitmapDownload.BeginDownload(System.Windows.Media.Imaging.BitmapDecoder, System.Uri, System.Net.Cache.RequestCachePolicy, System.IO.Stream)+0x502
  33: 011EDF20 53B77A3F PresentationCore_ni!System.Windows.Media.Imaging.LateBoundBitmapDecoder..ctor(System.Uri, System.Uri, System.IO.Stream, System.Windows.Media.Imaging.BitmapCreateOptions, System.Windows.Media.Imaging.BitmapCacheOption, System.Net.Cache.RequestCachePolicy)+0xf3
  34: 011EDF44 53D015FD PresentationCore_ni!System.Windows.Media.Imaging.BitmapDecoder.CreateFromUriOrStream(System.Uri, System.Uri
  35: EXCEPTION_OBJECT: !pe 12d8faa8
  36: Exception object: 12d8faa8
  37: Exception type: System.Deployment.Application.InvalidDeploymentException
  38: Message: Application identity is not set.
  39: InnerException: <none>
  40: StackTrace (generated):
  41: <none>
  42: StackTraceString: <none>
  43: HResult: 80131501
  44:  
  45: MANAGED_OBJECT: !dumpobj 33ec694
  46: Name: System.String
  47: MethodTable: 790fd8c4
  48: EEClass: 790fd824
  49: Size: 82(0x52) bytes
  50:  (C:WindowsassemblyGAC_32mscorlib2.0.0.0__b77a5c561934e089mscorlib.dll)
  51: String: Application identity is not set.
  52: Fields:
  53:       MT    Field   Offset                 Type VT     Attr    Value Name
  54: 79102290  4000096        4         System.Int32  1 instance       33 m_arrayLength
  55: 79102290  4000097        8         System.Int32  1 instance       32 m_stringLength
  56: 790ff328  4000098        c          System.Char  1 instance       41 m_firstChar
  57: 790fd8c4  4000099       10        System.String  0   shared   static Empty
  58:     >> Domain:Value  01300268:790d884c <<
  59: 7912dd40  400009a       14        System.Char[]  0   shared   static WhitespaceChars
  60:     >> Domain:Value  01300268:02ca1388 <<
  61:  
  62: EXCEPTION_MESSAGE:  Application identity is not set.
  63:  
  64: LAST_CONTROL_TRANSFER:  from 79f071ac to 766742eb
  65:  
  66: PRIMARY_PROBLEM_CLASS:  CLR_EXCEPTION
  67:  
  68: BUGCHECK_STR:  APPLICATION_FAULT_CLR_EXCEPTION
  69:  
  70: STACK_TEXT:  
  71: 6a325fc8 System_Deployment_ni!System.Deployment.Application.ApplicationDeployment.get_CurrentDeployment
  72: 6a326056 System_Deployment_ni!System.Deployment.Application.ApplicationDeployment.get_IsNetworkDeployed
  73: 53943bd2 PresentationCore_ni!MS.Internal.AppModel.SiteOfOriginContainer.get_SiteOfOriginForBrowserApplications
  74: 539e289d PresentationCore_ni!MS.Internal.PresentationCore.SecurityHelper.ExtractUriForClickOnceDeployedApp
  75: 539e28dc PresentationCore_ni!MS.Internal.PresentationCore.SecurityHelper.BlockCrossDomainForHttpsApps
  76: 53b6908a PresentationCore_ni!System.Windows.Media.Imaging.BitmapDownload.BeginDownload
  77: 53b77a3f PresentationCore_ni!System.Windows.Media.Imaging.LateBoundBitmapDecoder..ctor
  78:  
  79:  
  80: FOLLOWUP_IP: 
  81: System_Deployment_ni+5fc8
  82: 6a325fc8 8b4dd4          mov     ecx,dword ptr [ebp-2Ch]
  83:  
  84: SYMBOL_STACK_INDEX:  0
  85:  
  86: SYMBOL_NAME:  System_Deployment_ni!System.Deployment.Application.ApplicationDeployment.get_CurrentDeployment+5fc8
  87:  
  88: FOLLOWUP_NAME:  MachineOwner
  89:  
  90: MODULE_NAME: System_Deployment_ni
  91:  
  92: IMAGE_NAME:  System.Deployment.ni.dll
  93:  
  94: DEBUG_FLR_IMAGE_TIMESTAMP:  47577e63
  95:  
  96: STACK_COMMAND:  dds 11eddf4 ; kb
  97:  
  98: FAILURE_BUCKET_ID:  CLR_EXCEPTION_e0434f4d_System.Deployment.ni.dll!System.Deployment.Application.ApplicationDeployment.get_CurrentDeployment
  99:  
 100: BUCKET_ID:  APPLICATION_FAULT_CLR_EXCEPTION_System_Deployment_ni!System.Deployment.Application.ApplicationDeployment.get_CurrentDeployment+5fc8
 101:  
 102: Followup: MachineOwner

La pila nos encontramos con MS.Internal.PresentationCore.SecurityHelper.BlockCrossDomainForHttpsApps(System.Uri) que llama a MS.Internal.PresentationCore.SecurityHelper.ExtractUriForClickOnceDeployedApp(), ahí está todo el problema, resulta que para descargarse una imagen el framewokr necesita comprobar que la aplicación esta desplegada con ClicOnce, pero ¿Porqué?. Si es una aplicación de escritorio normal porque tiene que hacer eso si simplemente lo que quiero es descargar la imagen. Si vemos la implementación de ese método nos encontramos con esto:



   1: internal static void BlockCrossDomainForHttpsApps(Uri uri)
   2: {
   3:     Uri uri2 = ExtractUriForClickOnceDeployedApp();
   4:     if ((uri2 != null) && (uri2.Scheme == Uri.UriSchemeHttps))
   5:     {
   6:         if (uri.IsUnc || uri.IsFile)
   7:         {
   8:             new FileIOPermission(FileIOPermissionAccess.Read, uri.LocalPath).Demand();
   9:         }
  10:         else
  11:         {
  12:             new WebPermission(NetworkAccess.Connect, BindUriHelper.UriToString(uri)).Demand();
  13:         }
  14:     }
  15: }
  16:  
  17:  
  18:  
  19:  
  20:  
  21:  
  22:  
  23: internal static Uri SiteOfOriginForBrowserApplications
  24: {
  25:     [FriendAccessAllowed]
  26:     get
  27:     {
  28:         Uri deploymentUri = null;
  29:         if (_debugSecurityZoneURL.Value != null)
  30:         {
  31:             return _debugSecurityZoneURL.Value;
  32:         }
  33:         if (_browserSource.Value != null)
  34:         {
  35:             return _browserSource.Value;
  36:         }
  37:         if (ApplicationDeployment.IsNetworkDeployed)
  38:         {
  39:             deploymentUri = GetDeploymentUri();
  40:         }
  41:         return deploymentUri;
  42:     }
  43: }

En el que al final del todo se llama a ApplicationDeployment.IsNetworkDeployed causando que la aplicación lanza excepciones.


¿ Cómo se puede puede solucionar esto ?


Pues de una manera muy sencilla, puesto que el problema es que el propio descargador de Bitmaps tiene que comprobar la seguridad si o sí pues descargemos nosotros esa imagen en memoria y dejemos al framework que la cargue desde un MemoryStream, así:



   1: WebClient wc = new WebClient();
   2: byte[] data = wc.DownloadData(new Uri(“http://localhost/img.jpg”));
   3:  
   4: MemoryStream ms = new MemoryStream(data);
   5:  
   6: BitmapImage bi = new BitmapImage();
   7: bi.BeginInit();
   8:  
   9: bi.StreamSource = ms;
  10:  
  11: bi.EndInit();

Luis.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *