Organização simples de tarefas, assíncronas e aguardadas, e noções básicas de Thread usadas no Unity C#

Organização simples de tarefas, assíncronas e aguardadas, e noções básicas de Thread usadas no Unity C#

Índice

Organização simples de tarefas, assíncronas e aguardadas, e noções básicas de Thread usadas no Unity C#

1. Conceitos básicos de tarefa, assíncrona e espera, Thread

1. Thread, multithread

2、Tarefa

 3. assíncrono (aguardar)

2. Uso básico de Tarefa, criação e inicialização de Tarefa

1. Crie e execute uma tarefa

2. Crie uma tarefa com valor de retorno

3. Task fornece task.RunSynchronously() para execução síncrona de tarefas de Task

3. Método de bloqueio de tarefas (Wait/WaitAll/WaitAny)

1. Thread bloqueia o método de thread principal: método thread.Join()

2. A tarefa fornece métodos Wait/WaitAny/WaitAll, que podem controlar o bloqueio de thread de forma mais conveniente

4. A operação de continuação da tarefa (WhenAny/WhenAll/ContinueWith)

1. Operação de continuação de tarefa (WhenAny/WhenAll/ContinueWith)

 2. A operação de continuação de Task.Factory (WhenAny/WhenAll/ContinueWith)

5. Cancelamento de tarefa de tarefa (CancellationTokenSource)

1. Cancelamento de tarefa de thread

 2. Cancelamento de tarefa

3. CancellationTokenSource por source.Token.Register e source.CancelAfter(XXXXms)

 5. Método assíncrono (async/await)

Seis, tarefa, assíncrona e espera, resumo simples do thread


1. Conceitos básicos de tarefa, assíncrona e espera, Thread

1. Thread, multithread

Thread (Thread) é a unidade básica de execução em um processo e a unidade básica para o sistema operacional alocar tempo de CPU.Um processo pode conter vários threads, e o primeiro thread executado na entrada do processo é considerado o thread principal do processo .

Em aplicativos .NET, o método Main() é usado como ponto de entrada. Quando esse método é chamado, o sistema criará automaticamente um thread principal. Threads são compostos principalmente de registros de CPU, pilhas de chamadas e armazenamento local de threads (Thread Local Storage, TLS) .

O registro da CPU registra principalmente o status do thread em execução no momento, a pilha de chamadas é usada principalmente para manter a memória e os dados chamados pelo thread, e o TLS é usado principalmente para armazenar as informações de status do thread.

Multithreading: Várias tarefas podem ser concluídas ao mesmo tempo; o programa pode responder mais rapidamente; tarefas que ocupam muito tempo de processamento ou tarefas que não estão sendo processadas no momento podem ceder periodicamente o tempo de processamento para outras tarefas; as tarefas podem ser interrompidas em a qualquer momento; Defina a prioridade de cada tarefa para otimizar o desempenho do programa.

class Program
    {
        static void Main(string[] args)
        {
            //创建无参的线程
            Thread thread1 = new Thread(new ThreadStart(Thread1));
            //调用Start方法执行线程
            thread1.Start();

            Console.ReadKey();
        }

        /// <summary>
        /// 创建无参的方法
        /// </summary>
        static void Thread1()
        {
            Console.WriteLine("这是无参的方法");
        }
    }

2、Tarefa

A tarefa é iniciada com base no ThreadPool. Vamos entender brevemente o ThreadPool.

  • Há um certo número de threads no ThreadPool. Se houver uma tarefa a ser processada, um thread ocioso será obtido do pool de threads para executar a tarefa. Depois que a tarefa for executada, o thread não será destruído, mas será ser reciclado pelo pool de threads para tarefas subsequentes.
  • Quando todos os threads no pool de threads estiverem ocupados e houver uma nova tarefa a ser processada, o pool de threads criará um novo thread para processar a tarefa
  • Se o número de threads atingir o valor máximo definido, a tarefa será enfileirada e aguardará que outras tarefas liberem threads antes da execução.
  • O pool de threads pode reduzir a criação de threads e economizar sobrecarga. Veja o uso de um ThreadPool da seguinte maneira
 static void Main(string[] args)
 {
       for (int i = 1; i <=10; i++)
       {
                //ThreadPool执行任务
                ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {
                    Console.WriteLine($"第{obj}个执行任务");
                }),i);
       }
     Console.ReadKey();
}

O código acima executa 10 tarefas por meio de ThreadPool e o resultado da execução é

 3. assíncrono (aguardar)

async  é usado principalmente para modificar métodos.

Quando um método é chamado, o chamador precisa esperar que o método seja concluído e retorne antes de continuar . Chamamos esse método de método síncrono;

Quando um método é chamado, ele retorna imediatamente e obtém um thread para executar o negócio interno do método. O chamador não precisa esperar a conclusão do método. Chamamos esse método de método assíncrono.

A vantagem da assincronia é que ela não bloqueia (o thread de chamada não suspenderá a execução para aguardar a conclusão do subthread), portanto, definimos algumas tarefas demoradas que não precisam usar os resultados imediatamente para serem executadas de forma assíncrona, o que pode melhorar a eficiência de execução do programa.

Palavras-chave assíncronas e aguardadas em C# , a programação assíncrona é muito popular.

1) Quando estamos lidando com a UI, estamos usando um método de longa execução quando um botão é clicado, como ler um arquivo grande ou algo que leva muito tempo

2) Neste caso toda a aplicação deverá aguardar o término de toda a tarefa.

3) Em outras palavras, se algum processo da aplicação síncrona for bloqueado, toda a aplicação será bloqueada e nossa aplicação parará de responder até que toda a tarefa seja concluída.

A programação assíncrona é muito útil nesta situação. Ao utilizar a programação assíncrona, a aplicação pode continuar a realizar outros trabalhos que não dependem da conclusão de toda a tarefa.

Net4.0 lançou a classe Task com base em ThreadPool . A Microsoft recomenda fortemente o uso de Task para executar tarefas assíncronas. Agora, os métodos assíncronos na biblioteca de classes C# usam basicamente Task;

net5.0 introduziu async/await para tornar a programação assíncrona mais conveniente.

2. Uso básico de Tarefa, criação e inicialização de Tarefa

Conhecemos as desvantagens do ThreadPool:

(1) Não podemos controlar a ordem de execução dos threads no pool de threads,

(2) Também é impossível obter notificações de cancelamento/anormalidade/conclusão de thread no pool de threads.

net4.0 lançou Task com base em ThreadPool

(1) A tarefa tem as vantagens do pool de threads

(2) Ao mesmo tempo, também resolve as desvantagens de usar o pool de threads que não é fácil de controlar

1. Crie e execute uma tarefa

Vamos primeiro ver como criar e executar uma tarefa. Existem três maneiras de criar e iniciar uma tarefa:

  • Tarefa tarefa = new Tarefa(); tarefa.Iniciar();
  • Tarefa tarefa = Task.Factory.StartNew();
  • Tarefa tarefa = Task.Run();
 		 static void Main(string[] args)
        {
            //1.new方式实例化一个Task,需要通过Start方法启动
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
            });
            task.Start();

            //2.Task.Factory.StartNew(Action action)创建和启动一个Task
            Task task2 = Task.Factory.StartNew(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
              });

            //3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
            Task task3 = Task.Run(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
              });
            Console.WriteLine("执行主线程!");
            Console.ReadKey();
        }

Os resultados da execução são os seguintes: podemos ver que "executar o thread principal" é impresso primeiro e depois imprimir cada tarefa, o que mostra que a tarefa não bloqueará o thread principal:

2. Crie uma tarefa com valor de retorno

No código acima, Task não tem valor de retorno. Também podemos criar uma Task com valor de retorno . O uso é basicamente o mesmo que sem valor de retorno. Vamos simplesmente modificar a castanha acima. O código é o seguinte:

 Task<tipo de dados>, task.Result obtém o resultado

Vale ressaltar que: task.Resut irá bloquear o thread ao obter o resultado, ou seja, se a tarefa não for executada, ele irá aguardar a conclusão da tarefa e obter o Resultado, para então executar o seguinte código

  • Tarefa<string> tarefa = new Tarefa(); tarefa.Iniciar();
  • Tarefa<string> tarefa = Task.Factory.StartNew();
  • Tarefa<string> tarefa = Task.Run();
         static void Main(string[] args)
        {
            //1.new方式实例化一个Task,需要通过Start方法启动
            Task<string> task = new Task<string>(() =>
            {
                return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";
            });
            task.Start();

            //2.Task.Factory.StartNew(Func func)创建和启动一个Task
           Task<string> task2 =Task.Factory.StartNew<string>(() =>
            {
                return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";
            });

            //3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个Task
           Task<string> task3= Task.Run<string>(() =>
            {
                return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";
            });

            Console.WriteLine("执行主线程!");
            Console.WriteLine(task.Result);
            Console.WriteLine(task2.Result);
            Console.WriteLine(task3.Result);
            Console.ReadKey();
        }

O resultado da operação é o seguinte:

3. Task fornece task.RunSynchronously() para execução síncrona de tarefas de Task

A execução de Task em todos os códigos acima é assíncrona e não bloqueará o thread principal. Em alguns cenários, o que devemos fazer se quisermos que a tarefa seja executada de forma síncrona? Task fornece task.RunSynchronously() para executar tarefas de Task de forma síncrona

  • Tarefa tarefa = new Tarefa(); task.RunSynchronously();
        static void Main(string[] args)
        {
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("执行Task结束!");
            });
            //同步执行,task会阻塞主线程
            task.RunSynchronously();
            Console.WriteLine("执行主线程结束!");
            Console.ReadKey();
        }

O resultado da operação é o seguinte:

3. Método de bloqueio de tarefas (Wait/WaitAll/WaitAny)

1. Thread bloqueia o método de thread principal: método thread.Join()

O método Join do Thread pode bloquear o thread de chamada , mas existem algumas desvantagens:

Se quisermos implementar o bloqueio de muitos threads, cada thread deve chamar o método Join uma vez;

②Se quisermos desbloquear imediatamente quando todos os threads forem executados (ou qualquer thread for executado), não é fácil usar o método Join.

O código de referência é o seguinte

          static void Main(string[] args)
        {
            Thread th1 = new Thread(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            th1.Start();
            Thread th2 = new Thread(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            th2.Start();
            //阻塞主线程
            th1.Join();
            th2.Join();
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

O resultado da operação é o seguinte:

2. A tarefa fornece métodos Wait/WaitAny/WaitAll, que podem controlar o bloqueio de thread de forma mais conveniente

  • Task.Wait() significa aguardar a execução da tarefa e sua função é semelhante a thead.Join();
  • Task.WaitAll(Task[] tarefas) significa que somente quando todas as tarefas forem executadas, o bloco será desbloqueado;
  • Task.WaitAny ( Task[] tarefas ) significa que enquanto uma tarefa for executada, ela será desbloqueada;

O código de referência é o seguinte:

        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //阻塞主线程。task1,task2都执行完毕再执行主线程
            //执行【task1.Wait();task2.Wait();】可以实现相同功能
            Task.WaitAll(new Task[] { task1, task2 });
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

O resultado da operação é o seguinte

4. A operação de continuação da tarefa (WhenAny/WhenAll/ContinueWith)

1. Operação de continuação de tarefa (WhenAny/WhenAll/ContinueWith)

O valor de retorno dos métodos Wait/WaitAny/WaitAll acima é nulo e esses métodos simplesmente implementam threads de bloqueio .

Agora pensamos: depois que todas as tarefas forem executadas (ou alguma tarefa for executada), comece a realizar as operações de acompanhamento, como fazer isso?

Neste momento, o método WhenAny/WhenAll pode ser usado e esses métodos retornam uma instância de tarefa após a execução.

  • task.WhenAll(Task[] tarefas) indica que todas as tarefas são executadas antes de realizar operações subsequentes
  • task.WhenAny(Task[] tarefas) Indica que após qualquer tarefa ser executada, as operações subsequentes serão executadas

O código de referência é o seguinte:

        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //task1,task2执行完了后执行后续操作
            Task.WhenAll(task1, task2).ContinueWith((t) => {
                Thread.Sleep(100);
                Console.WriteLine("执行后续操作完毕!");
            });

            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

O resultado da operação é o seguinte:

 2. A operação de continuação de Task.Factory (WhenAny/WhenAll/ContinueWith)

As castanhas acima também podem ser implementadas por Task.Factory.ContinueWhenAll(Task[] tasks, Action continuationAction) e Task.Factory.ContinueWhenAny(Task[] tasks, Action continuationAction)

Modifique o código acima da seguinte maneira

        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //通过TaskFactroy实现
            Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>
            {
                Thread.Sleep(100);
                Console.WriteLine("执行后续操作");
            });

            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

O resultado da execução permanece inalterado, como segue

5. Cancelamento de tarefa de tarefa (CancellationTokenSource)

1. Cancelamento de tarefa de thread

Antes da tarefa, usamos Thread para executar a tarefa. Como o Thread cancela a tarefa?

O processo geral é:

  • 1) Defina uma variável para controlar se a tarefa será interrompida, como definir uma variável isStop
  • 2) Em seguida, o thread pesquisa para verificar isStop e para se isStop for verdadeiro

O código de referência é o seguinte:

        static void Main(string[] args)
        {
            bool isStop = false;
            int index = 0;
            //开启一个线程执行任务
            Thread th1 = new Thread(() =>
            {
                while (!isStop)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"第{++index}次执行,线程运行中...");
                }
            });
            th1.Start();
            //五秒后取消任务执行
            Thread.Sleep(5000);
            isStop = true;
            Console.ReadKey();
        }

O resultado da operação é o seguinte

 2. Cancelamento de tarefa

Existe uma classe especial CancellationTokenSource em Task para cancelar a execução da tarefa

Ainda usando o exemplo acima, ajuste o código da seguinte forma

        static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            int index = 0;
            //开启一个task执行任务
            Task task1 = new Task(() =>
            {
                while (!source.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"第{++index}次执行,线程运行中...");
                }
            });
            task1.Start();
            //五秒后取消任务执行
            Thread.Sleep(5000);
            //source.Cancel()方法请求取消任务,IsCancellationRequested会变成true
            source.Cancel();
            Console.ReadKey();
        }

O efeito da execução do programa permanece inalterado, como segue

3. CancellationTokenSource por source.Token.Register e source.CancelAfter(XXXXms)

A função de CancellationTokenSource não é apenas cancelar a execução da tarefa , podemos usar

  • source.CancelAfter(5000) cancela automaticamente a tarefa após 5 segundos
  • source.Token.Register(Action action) pode registrar a função de callback acionada pelo cancelamento da tarefa, ou seja, a ação registrada será executada quando a tarefa for cancelada

O código de referência é o seguinte

        static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            //注册任务取消的事件
            source.Token.Register(() =>
            {
                Console.WriteLine("任务被取消后执行xx操作!");
            });

            int index = 0;
            //开启一个task执行任务
            Task task1 = new Task(() =>
            {
                while (!source.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"第{++index}次执行,线程运行中...");
                }
            });
            task1.Start();
            //延时取消,效果等同于Thread.Sleep(5000);source.Cancel();
            source.CancelAfter(5000);
            Console.ReadKey();
        }

O resultado da operação é o seguinte

 5. Método assíncrono (async/await)

A aparência de async e await no C# 5.0 facilita a programação assíncrona.

Essa abordagem aproveita o suporte assíncrono no .NET Framework 4.5 e posterior, no .NET Core e no Windows Runtime.

O compilador faz o trabalho árduo que os desenvolvedores fizeram e o aplicativo mantém uma estrutura lógica semelhante ao código síncrono.

  • async é usado antes da definição do método e await só pode ser escrito em métodos marcados com async.
  • Preste atenção onde o await espera de forma assíncrona. O código por trás do await pode ser executado em um thread diferente do código anterior.
  • A palavra-chave async cria uma máquina de estado, semelhante à instrução yield return; await desbloqueará o thread atual e concluirá outras tarefas

Veja como ler o conteúdo do arquivo de forma síncrona e assíncrona.

Podemos ver que o código de leitura assíncrona é basicamente igual ao código de leitura síncrona. async/await torna a codificação assíncrona mais fácil, podemos escrever código assíncrono como código síncrono.

Preste atenção a um pequeno problema: o valor de retorno da assinatura do método no método assíncrono é Task, e o valor de retorno no código é T.

O valor de retorno da assinatura de GetContentAsync no código é Task, enquanto o valor de retorno no código é string. Manter esse detalhe em mente é útil ao analisar código assíncrono.

O valor de retorno da assinatura do método assíncrono possui os três tipos a seguir:

  • Tarefa: Se o método chamador deseja obter um valor de retorno do tipo T chamando um método assíncrono, a assinatura deve ser Tarefa;
  • Tarefa: Se o método de chamada não deseja obter um valor por meio do método assíncrono, mas apenas deseja rastrear o status de execução do método assíncrono, podemos definir o valor de retorno da assinatura do método assíncrono como Tarefa;
  • void: Se o método de chamada for apenas para chamar o método assíncrono e não interagir com o método assíncrono, podemos definir o valor de retorno da assinatura do método assíncrono como void, que também é chamado de "chamar e esquecer".

O código de referência é o seguinte

        static void Main(string[] args)
        {
            //Console.WriteLine(" Environment.CurrentDirectory "+ Environment.CurrentDirectory);
            string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result;
            //调用同步方法
            //string content = GetContent(Environment.CurrentDirectory + @"/test.txt");
            Console.WriteLine(content);
            Console.ReadKey();
        }
 
        /// <summary>
        /// 异步读取文件内容
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        async static Task<string> GetContentAsync(string filename)
        {

            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //ReadAync方法异步读取内容,不阻塞线程
            Console.WriteLine("开始读取文件");
            int len = await fs.ReadAsync(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }

        /// <summary>
        /// 同步读取文件内容
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        static string GetContent(string filename)
        {
            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //Read方法同步读取内容,阻塞线程
            int len = fs.Read(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }

O resultado da operação é o seguinte:

Seis, tarefa, assíncrona e espera, resumo simples do thread

Por fim, simplesmente lembramos de alguns recursos, para que possamos entendê-los melhor:

  • 1) async/await é baseado em Task
  • 2) Task é uma melhoria no encapsulamento do ThreadPool, principalmente para um controle mais eficaz dos threads no pool de threads (threads no ThreadPool, é difícil para nós controlar sua ordem de execução, continuação e cancelamento de tarefas, etc. através do código);
  • 3) ThreadPool é baseado em Thread, o objetivo principal é reduzir o número de criação de Thread e o custo de gerenciamento de Thread.
  • 4) Async/await Task é mais avançada em C# e também é um recurso promovido pela Microsoft
  • 5) Podemos tentar usar Task em vez de Thread/ThreadPool durante o desenvolvimento. Para lidar com tarefas de IO locais e de rede, tente usar async/await para melhorar a eficiência de execução de tarefas.

Acho que você gosta

Origin blog.csdn.net/u014361280/article/details/132401320
Recomendado
Clasificación