A partir de C# 5.0: resuma de nuevo el historial de desarrollo del método de llamada asincrónica de C#

Este artículo continúa introduciendo el uso de la clase WaitHandler y sus subclases Mutex, ManualResetEvent y AutoResetEvent.

Hay tantas formas de sincronizar hilos en .NET que la gente queda deslumbrada ¿Cómo debemos entenderlo?

De hecho, cuando observamos la sincronización de subprocesos independientemente del entorno .NET, no es más que realizar dos operaciones:

  • Uno es exclusión/bloqueo mutuo, el propósito es asegurar la "atomicidad" de las operaciones de código de sección crítica;
  • La otra es la operación de semáforo, el propósito es garantizar que se ejecuten varios subprocesos en un orden determinado, como que el subproceso productor debe ejecutarse antes que el subproceso consumidor.

La clase de sincronización de subprocesos en .NET no es más que la encapsulación de estos dos métodos.En el análisis final, el propósito se puede atribuir a los dos métodos de exclusión/bloqueo mutuo o semáforo, pero sus ocasiones aplicables son diferentes.

A continuación, entendemos WaitHandler y sus subclases según la jerarquía de clases.

1. Controlador de espera

WaitHandle es el ancestro común de Mutex, Semaphore, EventWaitHandler, AutoResetEvent y ManualResetEvent.Encapsula el objeto del kernel del controlador de sincronización de Win32, es decir, es la versión administrada de estos objetos del kernel.

Un subproceso puede bloquearse en un solo identificador de espera llamando al método WaitOne de la instancia de WaitHandler. Además, la clase WaitHandler sobrecarga el método estático para esperar a que todos los controladores de espera especificados hayan recopilado la señal WaitAll, o para esperar a que un controlador de espera especificado recopile la señal WaitAny. Todos estos métodos brindan la oportunidad de abandonar el intervalo de tiempo de espera de espera, salir del contexto de sincronización antes de ingresar a la espera y permitir que otros subprocesos usen el contexto de sincronización. WaitHandler es una clase abstracta en C# y no se puede crear una instancia.

2.EventWaitHandler frente a ManualResetEvent frente a AutoResetEvent (evento síncrono)

Primero veamos la implementación de las dos subclases ManualResetEvent y AutoResetEvent en .NET Framework: //
La implementación de la clase ManualResetEvent en .NET Framework [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)] clase pública sellada ManualResetEvent : EventWaitHandle { // Métodos public ManualResetEvent(bool initialState) : base(initialState, EventResetMode.ManualReset) { } } //Implementación de la clase AutoResetEvent en .NET Framework [ComVisible(true), HostProtection(SecurityAction .LinkDem and, Synchronization = true, ExternalThreading = true)] clase pública sellada AutoResetEvent : EventWaitHandle { // Métodos public AutoResetEvent(bool initialState) : base(initialState, EventResetMode.AutoReset) { } }

Resulta que tanto ManualResetEvent como AutoResetEvent heredan de EventWaitHandler. La única diferencia entre ellos es que el parámetro constructor EventResetMode de la clase padre EventWaitHandler es diferente. De esta manera, solo necesitamos encontrar la diferencia en el comportamiento del control de la clase EventWaitHandler. sincronización de subprocesos cuando el valor del parámetro EventResetMode es diferente.La clase también es clara. Para facilitar la descripción, no presentamos los dos modos de la clase principal, sino que presentamos directamente la subclase.

Qué tienen en común ManualResetEvent y AutoResetEvent:

1) El método Set establece el estado del evento en un estado terminado, lo que permite que continúen uno o más subprocesos en espera; el método Reset establece el estado del evento en un estado no terminado, lo que hace que el subproceso se bloquee; WaitOne bloquea el subproceso actual hasta que WaitHandler del subproceso actual recibe la señal de evento.
2) El estado inicial puede ser determinado por el valor del parámetro del constructor. Si es verdadero, el evento terminará y el subproceso estará en un estado sin bloqueo. Si es falso, el subproceso estará bloqueado. estado.
3) Si un subproceso llama al método WaitOne, cuando finaliza el estado del evento, el subproceso recibirá una señal y continuará ejecutándose hacia abajo.

Diferencias entre ManualResetEvent y AutoResetEvent:

1) AutoResetEvent.WaitOne() solo permite que un subproceso ingrese a la vez. Cuando un subproceso recibe una señal, AutoResetEvent establecerá automáticamente la señal en el estado de no envío, y otros subprocesos que llaman a WaitOne solo pueden continuar esperando, eso es decir, AutoResetEvent una vez Solo activa un subproceso;
2) ManualResetEvent puede activar varios subprocesos, porque cuando un subproceso llama a ManualResetEvent, no envía.
3) Es decir, a menos que se llame manualmente al método ManualResetEvent.Reset(), ManualResetEvent siempre permanecerá señalado, y ManualResetEvent puede activar varios subprocesos al mismo tiempo para continuar la ejecución.

Escenario de ejemplo: dos buenos amigos, Zhang San y Li Si, van a cenar a un restaurante. Piden un pollo Kung Pao. El pollo Kung Pao tarda un tiempo en estar listo. Zhang San y Li Si no quieren ser estúpido, etc. Jugando juegos móviles, pensé que el pollo Kung Pao está listo y el camarero definitivamente nos llamará. Después de que el mesero sirvió la comida, Zhang San y Li Si comenzaron a disfrutar de la deliciosa comida. Cuando la comida se acabó, le pidieron al mesero que viniera y pagara la cuenta. Podemos abstraer tres hilos de esta escena, el hilo de Zhang San, el hilo de Li Si y el hilo del camarero. Deben estar sincronizados: el camarero sirve -> Zhang San y Li Si comienzan a disfrutar del pollo Kung Pao -> después de comer Pregúntale al mesero para que venga y pague la cuenta. ¿Para qué sirve esta sincronización? ¿ManualResetEvent o AutoResetEvent? No es difícil ver del análisis anterior que debemos usar ManualResetEvent para la sincronización. El siguiente es el código del programa:
https://www.cnblogs.com/dotnet261010/p/6055092.html
La historia de Zhang San y Li Si comiendo
—————————— ———————
C# ha sido desarrollado desde la versión inicial 1.0 hasta la versión 5.0, su historial de evolución es el siguiente, refiriéndonos a C# 5.0 EN POCAS PALABRAS:
inserte la descripción de la imagen aquí

Repasemos lo que trae cada versión:

  1. Versión 1.0: sintaxis básica de C#.
  2. Versión 2.0: soporte para genéricos, CLR se ha actualizado para admitir fundamentalmente genéricos de tiempo de ejecución.
  3. Versión 3.0: LINQ, se agregaron palabras clave similares a SQL, como from / join, se agregaron funciones de extensión y se agregó la palabra clave var de tipo dinámico en tiempo de compilación.
  4. Versión 4.0: palabra clave dinámica, se actualiza CLR, se agrega DLR y se inicia el soporte dinámico. Al mismo tiempo, se agregan funciones como parámetros dinámicos, valores predeterminados de parámetros y covarianza genérica.
  5. Versión 5.0: un nuevo modelo asincrónico que agrega palabras clave como async/await para simplificar la computación paralela Parallel.
    Se puede ver que C# como lenguaje de programación ya es muy poderoso.Con el desarrollo de los tiempos, C# sigue avanzando. Cada generación de C# traerá una nueva característica además del ajuste de la gramática. Desde los genéricos de 2.0, LINQ de 3.0, dinámico de 4.0 hasta asíncrono de 5.0, cada versión de C# tiene una idea dominante, y la mejora y el ajuste de otros detalles se admiten en torno a esta idea principal.
    Echemos un vistazo a los diversos métodos de implementación de C# 5.0 y versiones anteriores, métodos de llamada asincrónica.
    Primero, veamos un método de sincronización ordinario, como sigue:
using System;
using System.Net;

namespace NoAsync
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");
        }

        static void ShowUriContent(string uri)
        {
    
    
            using (WebClient client = new WebClient())
            {
    
    
                string text = client.DownloadString(uri);
                Display(text);
            }
        }

        static void Display(string text)
        {
    
    
            Console.WriteLine(text.Length);
        }
    }
}

Los métodos síncronos pueden provocar el bloqueo de subprocesos.
Así que tenemos un método asíncrono, el método asíncrono más antiguo es el modo de inicio/finalización (hay cuatro métodos de implementación, consulte la publicación de blog anterior de DebugLZQ: Resumen de la programación asíncrona de .NET: cuatro modos de implementación).
Usamos el modo de recomendación Begin/End para encapsular este método síncrono para lograr llamadas asíncronas, de la siguiente manera:

using System;
using System.Threading;
using System.Net;

namespace AsyBeginEndNoEncapsulation
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");//原同步方法
            ShowUriContentAsync("http://www.cnblogs.com/DebugLZQ");//封装后的异步方法

            Thread.Sleep(5000);
        }
        //------进行异步封装
        public delegate void ShowUriContentDelegate(string text);
        static void ShowUriContentAsync(string uri)
        {
    
    
            ShowUriContentDelegate showUriContentDelegate = ShowUriContent;
            showUriContentDelegate.BeginInvoke(uri, ShowUriContentCompleted, showUriContentDelegate);
        }

        static void ShowUriContentCompleted(IAsyncResult result)
        {
    
    
            (result.AsyncState as ShowUriContentDelegate).EndInvoke(result);
        }
        //------原同步方法
        static void ShowUriContent(string uri)
        {
    
    
            using (WebClient client = new WebClient())
            {
    
    
                string text = client.DownloadString(uri);
                Display(text);
            }
        }

        static void Display(string text)
        {
    
    
            Console.WriteLine(text.Length);
        }
    }
}

El paquete más primitivo es así.
Puede usar nuevas características de C#, como Acción/Función, método anónimo, expresión Lambda, etc., la abreviatura (coescritura) es la siguiente:

using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
using System.Threading;
using System.Net;

namespace AsyBeginEndNoEncapsulationSimply
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");//原同步方法
            ShowUriContentAsync("http://www.cnblogs.com/DebugLZQ");  //进行异步封装
            ShowUriContentAsync1("http://www.cnblogs.com/DebugLZQ");//简化1:Action简化
            ShowUriContentAsync2("http://www.cnblogs.com/DebugLZQ");//简化2:匿名方法简化
            ShowUriContentAsync3("http://www.cnblogs.com/DebugLZQ");//简化3:Lambda简化
            

            Thread.Sleep(50000);
        }
        //------进行异步封装
        public delegate void ShowUriContentDelegate(string text);
        static void ShowUriContentAsync(string uri)
        {
    
    
            ShowUriContentDelegate showUriContentDelegate = ShowUriContent;
            showUriContentDelegate.BeginInvoke(uri, ShowUriContentCompleted, showUriContentDelegate);
        }

        static void ShowUriContentCompleted(IAsyncResult result)
        {
    
    
            (result.AsyncState as ShowUriContentDelegate).EndInvoke(result);
        }
        //------进行异步封装--简化1:Action简化
        static void ShowUriContentAsync1(string uri)
        {
    
    
            Action<string> showUriContentDelegate = ShowUriContent;
            showUriContentDelegate.BeginInvoke(uri, ShowUriContentCompleted1, showUriContentDelegate);
        }

        static void ShowUriContentCompleted1(IAsyncResult result)
        {
    
    
            (result.AsyncState as Action<string>).EndInvoke(result);
        }
        //------简化2:匿名方法简化
        static void ShowUriContentAsync2(string uri)
        {
    
    
            Action<string> showUriContentDelegate = delegate(string uri_)
            {
    
    
                using (WebClient client = new WebClient())
                {
    
    
                    string text = client.DownloadString(uri_);
                    Display(text);
                }
            };
            showUriContentDelegate.BeginInvoke(uri, delegate(IAsyncResult result) {
    
     (result.AsyncState as Action<string>).EndInvoke(result); }, showUriContentDelegate);
        }
        //------简化3:Lambda简化
        static void ShowUriContentAsync3(string uri)
        {
    
    
            Action<string> showUriContentDelegate = ( uri_)=>
            {
    
    
                using (WebClient client = new WebClient())
                {
    
    
                    string text = client.DownloadString(uri_);
                    Display(text);
                }
            };
            showUriContentDelegate.BeginInvoke(uri, (result) => {
    
     (result.AsyncState as Action<string>).EndInvoke(result); }, showUriContentDelegate);
        }       
       
        //---------------------原同步方法
        static void ShowUriContent(string uri)
        {
    
    
            using (WebClient client = new WebClient())
            {
    
    
                string text = client.DownloadString(uri);
                Display(text);
            }
        }

        static void Display(string text)
        {
    
    
            Console.WriteLine(text.Length);
        }
    }
}

El anterior es nuestro método de implementación más original y varias variantes que utilizan nuevas funciones.
Pero WebClient es la encapsulación de alto nivel de WebRequest, y .NET ya ha encapsulado este modo asíncrono para nosotros (es decir, algunos métodos síncronos no necesitan ser encapsulados por nosotros mismos).
Así que también podemos hacer lo siguiente:

 
using System;
using System.Threading;
using System.Net;

namespace AsyncBeginEndEncapsulation
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ShowUriContent("http://www.cnblogs.com/DebugLZQ");
            ShowUriContent2("http://www.cnblogs.com/DebugLZQ");

            Console.WriteLine("Main thread continue...");
            Thread.Sleep(5000);
        }

        static void ShowUriContent(string uri)
        {
    
    
            using (WebClient client = new WebClient())
            {
    
    
                client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Display);
                client.DownloadStringAsync(new Uri(uri));
            }
        }

        static void Display(object sender, DownloadStringCompletedEventArgs e)
        {
    
    
            Console.WriteLine(e.Result.Length);
        }

        //-------简化的写法
        static void ShowUriContent2(string uri)
        {
    
    
            using (WebClient client = new WebClient())
            {
    
    
                client.DownloadStringCompleted += (s, e) => {
    
     Console.WriteLine(e.Result.Length); };
                client.DownloadStringAsync(new Uri(uri));
            }
        }
    }
}

A partir de la encapsulación activa del modo Begin/End de .NET anterior, se puede ver que su propósito es simplificar la invocación de métodos asincrónicos, y el objetivo final es hacer que las invocaciones de métodos asincrónicos sean tan simples como las invocaciones de métodos sincrónicos que somos. familiar con.
C# 5.0 presenta dos palabras clave async y await para proporcionar un modo de llamada de método asíncrono más conciso.
Nuestra implementación es la siguiente (el método principal del punto de entrada de la consola no se puede marcar como asíncrono, por lo que usamos un programa Winform para demostrarlo):

using System;
using System.Windows.Forms;
using System.Net;

namespace AsyncAwaitWinForm
{
    
    
    public partial class Form1 : Form
    {
    
    
        public Form1()
        {
    
    
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
    
    
            int length = await ShowUriContentAsyncAwait("http://www.cnblogs.com/DebugLZQ");
            textBox1.Text = length.ToString();
        }

         //
        async Task<int> ShowUriContentAsyncAwait(string uri)
        {
    
    
            using (WebClient client = new WebClient())
            {
    
    
                string text = client.DownloadString(uri);
                return text.Length;
            }
        }     
    }
}

Cabe señalar que async y await requieren: Visual Studio 2010 + SP1 + Visual Studio Async CTP o Visual Studio 2012.

Actualización: Acerca de Parallel—Task, consulte la publicación de blog de seguimiento de DebugLZQ: Task and Parallel

actualización: para obtener detalles sobre Async await, consulte la publicación de blog de seguimiento de DebugLZQ:
async wait

Todos los programas de muestra anteriores están escritos por DebugLZQ y pueden ejecutarse normalmente, y los resultados son obvios, por lo que no se adjuntan capturas de pantalla en ejecución.
La tecnología continúa avanzando, y espero versiones futuras más emocionantes de C#6, 7, 8, X...
para compartir mi comprensión con todos, para comunicarnos y progresar juntos, y para mejorar continuamente la comprensión~

Actualización: Lea más
Diferencia entre Delegate.BeginInvoke y Thread.Start
Diferencias en las diferentes formas de hacer programas concurrentes

Supongo que te gusta

Origin blog.csdn.net/kalvin_y_liu/article/details/127536902
Recomendado
Clasificación