2012-06-11 11 views
7

Tengo una biblioteca C# que desea tener la capacidad de enviar/publicar trabajo en el hilo "principal" de la interfaz de usuario (si existe). Esta biblioteca puede ser utilizada por:Capturar el hilo principal SynchronizationContext o Dispatcher de una biblioteca

  • Una aplicación winforms
  • Una aplicación nativa (con IU)
  • Una aplicación de consola (sin interfaz de usuario)

En la biblioteca que había Me gusta capturar algo (un SynchronizationContext, un Dispatcher, un Scheduler de tareas u otra cosa) durante la inicialización, que me permitirá (en un momento posterior) enviar/publicar trabajo al hilo principal (si el hilo principal tiene esa habilidad). -es decir, tiene un mensaje de bomba). Por ejemplo, a la biblioteca le gustaría colocar alguna UI de Winforms en el hilo principal si y solo si la aplicación principal tiene la capacidad para llegar al hilo principal.

cosas que he intentado:

  1. Un SynchronizationContext:. La captura de esto funciona bien para una aplicación de Windows Forms (un WindowsFormsSynchronizationContext se instalará como el Current SynchronizationContext Esto también funciona bien para la aplicación de consola - desde Puedo detectar que el Current SynchronizationContext es nulo (y por lo tanto, sé que no tengo la capacidad de enviar/publicar trabajos en el hilo principal). El problema aquí es la aplicación de IU nativa: tiene la capacidad (es decir, tiene una bomba de mensajes), pero el contexto de Sincronización actual es nulo y, por lo tanto, no puedo diferenciarlo del caso de la aplicación de consola. Si pudiera diferenciarme, podría simplemente instale WindowsFormsSynchronizationContext en el hilo principal, y estoy listo para continuar.
  2. A Dispatcher: Capturando esto usando Current crea un nuevo SynchronizationContext. Por lo tanto, en todas las situaciones, recibiré un despachador. Sin embargo, para una aplicación de consola, el uso de Dispatcher.Invoke de un hilo de fondo se bloqueará (como se esperaba). Podría usar Dispatcher.FromThread (que no crea un despachador para el hilo si no existe uno). Pero la aplicación de UI nativa devolverá un Dispatcher nulo utilizando este método, y entonces, de nuevo, estoy atascado al no poder distinguir la aplicación de la interfaz de usuario de la aplicación de la consola.
  3. A TaskScheduler: Podría usar FromCurrentSynchronizationContext. Esto tiene los mismos problemas que SynchronizationContext. Es decir. Antes de llamar a FromCurrentSyncronizationContext, tendría que verificar si el Current SynchronizationContext es nulo (lo cual será el caso para la aplicación de consola y la aplicación ui nativa). Entonces, nuevamente, no puedo distinguir la aplicación de interfaz de usuario nativa de la aplicación de consola.

Yo, por supuesto, podría tener el usuario de la biblioteca de mi especifica si es o no es una aplicación de interfaz de usuario cuando llaman mi método Initialize, pero yo estaba esperando evitar esa complicación para el usuario de la biblioteca si es posible .

+0

Estoy usando la biblioteca C# en MFC. Esta biblioteca tiene la llamada TaskScheduler.FromCurrentSynchronizationContext(). cuando ejecuto la aplicación MFC lanza una excepción que dice que 'SynchronizationContext actual no se puede usar como TaskScheduler'. ¿Alguna idea de cómo manejarlo?No estoy utilizando directamente la biblioteca C# en MFC, pero he creado un contenedor en C++ administrado y estoy usando este contenedor en la aplicación MFC. –

Respuesta

5

Esto no es posible en general, una biblioteca que es apta para ser utilizada en hilos no puede hacer suposiciones sobre qué hilo en particular es el hilo de UI. Puede capturar Sincronización. Actual, pero eso solo funcionará correctamente si su método de inicialización es invocado desde el hilo de la interfaz de usuario. Eso no es terriblemente inusual para funcionar bien, como TaskScheduler. FromCurrentSynchronizationContext() tiende a funcionar por accidente, pero no es una garantía. Puede agregar un cheque, si Thread.CurrentThread.GetApartmentState() no devuelve STA, entonces las probabilidades de que no se le llame desde el hilo de la interfaz de usuario son muy altas. SynchronizationContext.Current también suele ser nulo en ese caso, otra forma de comprobarlo.

Las (mejor) mejores maneras son simplemente no preocuparse por ello y dejar que el cliente lo resuelva, no tendrá problemas para reunir la devolución de llamada. O para exponer una propiedad de tipo SynchronizationContext para que el código del cliente pueda asignarla. O agréguelo como un argumento constructor. Lanza una InvalidOperationException si estás listo para publicar, pero descubre que todavía es nula, eso es un descuido que el programador cliente solo hace una vez.

1

Creo que debe hacer esto una opción a su método Initialize (o de alguna manera permitir que la persona que llama para solicitar la interacción UI), para mí que simplemente tiene más sentido. No conozco los detalles, pero me parece que es una decisión "cortés", deje que su interlocutor decida si quiere o si desea respaldar su UI. Daría un paso más e incluso como la persona que llama para proporcionar un contexto de sincronización. Pero esa es mi opinión.

Para responder a su pregunta, existen algunos "hacks" que puede usar para determinar si se está ejecutando en una aplicación de consola.Esta pregunta SO tiene algo de información sobre eso: C#/.NET: Detect whether program is being run as a service or a console application

+0

Gracias. Para una aplicación MFC nativa, ¿qué SynchronizationContext debería proporcionar? Un WindowsFormsSynchronizationContext funcionaría muy bien, pero les parecería algo extraño que lo proporcionen. –

+1

Tendría que investigar un poco o consultar a alguien que esté más familiarizado con MFC, hace mucho tiempo que no he trabajado con MFC, así que ni siquiera estoy seguro. Lo que quiero decir es que no soy un gran fanático de "hornear" ese tipo de opciones, la apertura y la flexibilidad suelen ser una mejor opción. – CodingGorilla

0

Cambie la inicialización de la biblioteca para tener un parámetro SyncronizationContext. Si el parámetro es nulo, entonces la biblioteca no necesita hacer nada especial, si no es nulo, las actualizaciones de la GUI de envío/envío allí.

-1

Creo que esto es exactamente lo que AsyncOperationManager.CreateOperation() es para. “Implementing the Event-based Asynchronous Pattern” estados:

El patrón asincrónico basado en eventos proporciona una forma estandarizada para empaquetar una clase que tiene características asincrónicas. Si se implementa con clases de ayuda como AsyncOperationManager, su clase funcionará correctamente en cualquier modelo de aplicación, incluidas ASP.NET, aplicaciones de consola y aplicaciones de Windows Forms.

Depende de la persona que llama que decida si quiere llamar a su API en el hilo de la IU o no. Si lo hacen, esto capturará el contexto y los eventos pasarán por la bomba de mensajes en orden. En una aplicación de consola, puede obtener el mismo comportamiento si instala un SynchronizationContext que obtiene de forma gratuita utilizando AsyncContext.Run() del paquete nuget Nito.AsyncEx. No necesita una propiedad adicional ni tiene que escribir el código condicional usted mismo. Si no está disponible el contexto de sincronización de serialización, AsyncOperation.Post() usará el contexto de sincronización falsa disponible para las aplicaciones de la consola que simplemente ponen en cola el evento en el grupo de hilos en su lugar (lo que significa que las publicaciones pueden no ejecutarse en orden). Solo recuerda llamar al AsyncOperation.OperationCompleted() o AsyncOperation.PostOperationCompleted() cuando hayas terminado.

En la biblioteca me gustaría capturar algo (A SynchronizationContext, un despachador, un programador de tareas, o algo más) durante la inicialización

Esto es exactamente lo que hace AsyncOperationManager.CreateOperation(), y en un entorno modo augusto Pero debería intentar emparejar esto con una llamada al OperationCompleted(), que tal vez sería más difícil dada la API que desea exponer. La forma más fácil de usar AsyncOperation sería iniciar una operación cuando su biblioteca realmente inicie una operación en lugar de durante la inicialización. O al hacer que la rutina de inicialización devuelva un identificador de objeto de contexto IDisposable que indicaría al consumidor que necesita administrar su duración.

Cuestiones relacionadas