without understanding run practices method best await async c# multithreading synchronization async-await

c# - understanding - utilizando variables ThreadStatic con async/await



run method async c# (4)

Básicamente, me gustaría enfatizar: no hagas eso. [ThreadStatic] nunca se reproducirá bien con código que salta entre subprocesos.

Pero no tienes que hacerlo. Una Task ya tiene un estado, de hecho, puede hacerlo de dos maneras diferentes:

  • hay un objeto de estado explícito, que puede contener todo lo que necesita
  • los métodos lambdas / anon pueden formar cierres sobre el estado

Además, el compilador hace todo lo que necesita aquí de todos modos:

private static async Task Start() { string secret = "moo moo"; Console.WriteLine("Started on thread [{0}]", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Secret is [{0}]", secret); await Sleepy(); Console.WriteLine("Finished on thread [{0}]", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Secret is [{0}]", secret); }

No hay estado estático; No hay problemas con hilos o tareas múltiples. Simplemente funciona . Tenga en cuenta que el secret no es solo un "local" aquí; El compilador ha trabajado algo de vudú, como lo hace con los bloques de iteradores y las variables capturadas. Comprobando el reflector, me sale:

[CompilerGenerated] private struct <Start>d__0 : IAsyncStateMachine { // ... lots more here not shown public string <secret>5__1; }

Con las nuevas palabras clave async / await en C #, ahora hay impactos en la forma (y cuándo) de usar los datos de ThreadStatic, porque el delegado de devolución de llamada se ejecuta en un subproceso diferente a uno en el que comenzó la operación async . Por ejemplo, la siguiente aplicación de consola simple:

[ThreadStatic] private static string Secret; static void Main(string[] args) { Start().Wait(); Console.ReadKey(); } private static async Task Start() { Secret = "moo moo"; Console.WriteLine("Started on thread [{0}]", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Secret is [{0}]", Secret); await Sleepy(); Console.WriteLine("Finished on thread [{0}]", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("Secret is [{0}]", Secret); } private static async Task Sleepy() { Console.WriteLine("Was on thread [{0}]", Thread.CurrentThread.ManagedThreadId); await Task.Delay(1000); Console.WriteLine("Now on thread [{0}]", Thread.CurrentThread.ManagedThreadId); }

Saldrá algo a lo largo de la línea de:

Started on thread [9] Secret is [moo moo] Was on thread [9] Now on thread [11] Finished on thread [11] Secret is []

También he experimentado con el uso de CallContext.SetData y CallContext.GetData y obtuve el mismo comportamiento.

Después de leer algunas preguntas y temas relacionados:

Parece que marcos como ASP.Net migran explícitamente HttpContext a través de subprocesos, pero no a CallContext , así que quizás lo mismo esté sucediendo aquí con el uso de async y await palabras clave.

Con el uso de las palabras clave async / await en mente, ¿cuál es la mejor manera de almacenar los datos asociados con un subproceso de ejecución en particular que se puede restaurar (automáticamente) en el subproceso de devolución de llamada?

Gracias,


Echa un vistazo a este thread

En los campos marcados con ThreadStaticAttribute, la inicialización se realizará solo una vez. En su código, cuando se crea el nuevo hilo con ID 11, se creará un nuevo campo Secreto pero está vacío / nulo. Al regresar a la tarea "Inicio", la tarea finalizará en el hilo 11 (como se muestra en su impresión) y, por lo tanto, la cadena esta vacio.

Puede resolver su problema almacenando el Secreto en un campo local dentro de "Inicio" justo antes de llamar a Sleepy, luego restaure el Secreto desde el campo local después de regresar de Sleepy. También puedes hacerlo en Sleepy justo antes de llamar "aguarda Task.Delay (1000)"; que en realidad causa el cambio de hilo.


Lograr que una continuación de tarea se ejecute en el mismo subproceso requiere un proveedor de sincronización. Esa es una palabra costosa, el diagnóstico simple es mirar el valor de System.Threading.SynchronizationContext.Current en el depurador.

Ese valor será nulo en la aplicación de modo consola. No hay ningún proveedor que pueda hacer que el código se ejecute en un subproceso específico en una aplicación de modo de consola. Solo una aplicación Winforms o WPF o una aplicación ASP.NET tendrán un proveedor. Y solo en su hilo principal.

El hilo principal de estas aplicaciones hace algo muy especial, tienen un bucle de despachador (también conocido como bucle de mensajes o bomba de mensajes). Lo que implementa la solución general al problema productor-consumidor . Es ese bucle de despachador el que permite administrar un hilo un poco de trabajo para realizar. Un poco de trabajo será la continuación de la tarea después de la expresión de espera. Y ese bit se ejecutará en el hilo del despachador.

WindowsFormsSynchronizationContext es el proveedor de sincronización para una aplicación Winforms. Utiliza Control.Begin / Invoke () para enviar la solicitud. Para WPF es la clase DispatcherSynchronizationContext, usa Dispatcher.Begin / Invoke () para enviar la solicitud. Para ASP.NET, es la clase AspNetSynchronizationContext, que utiliza tuberías internas invisibles. Crean una instancia de sus respectivos proveedores en su inicialización y la asignan a SynchronizationContext.Current

No hay tal proveedor para una aplicación de modo consola. Principalmente porque el hilo principal es totalmente inadecuado, no utiliza un bucle de despachador. Usted tendría que crear su propia, y también crear su propia clase derivada SynchronizationContext. Difícil de hacer, no puedes hacer una llamada como Console.ReadLine () ya que eso congela completamente el hilo principal en una llamada de Windows. Su aplicación de modo de consola deja de ser una aplicación de consola, comenzará a parecerse a una aplicación de Winforms.

Tenga en cuenta que estos entornos de ejecución tienen proveedores de sincronización por una buena razón. Tienen que tener uno porque una GUI es fundamentalmente insegura de subprocesos. No es un problema con la consola, es seguro para subprocesos.


Puede usar CallContext.LogicalSetData y CallContext.LogicalGetData , pero le recomiendo que no lo haga porque no admiten ningún tipo de "clonación" cuando usa un paralelismo simple ( Task.WhenAny / Task.WhenAll ).

Abrí una solicitud de UserVoice para un "contexto" compatible con async más completo, que se explica con más detalle en una publicación del foro de MSDN . No parece posible construir uno nosotros mismos. Jon Skeet tiene una buena entrada de blog sobre el tema.

Por lo tanto, te recomiendo que uses argumentos, cierres lambda o los miembros de la instancia local ( this ), como lo describió Marc.

Y sí, OperationContext.Current no se conserva en await s.

Actualización: .NET 4.5 admite datos Logical[Get|Set]Data en código async . Detalles en mi blog .