在探讨同步非同步之前首先要了解何为thread, 以下内容抄录自维基百科。
执行绪(英语:thread)是作业系统能够进行运算排程的最小单位。大部分情况下,它被包含在行程之中,是行程中的实际运作单位。一条执行绪指的是行程中一个单一顺序的控制流,一个行程中可以并行多个执行绪,每条执行绪并列执行不同的任务。在Unix System V及SunOS中也被称为轻量行程(lightweight processes),但轻量行程更多指核心执行绪(kernel thread),而把使用者执行绪(user thread)称为执行绪。
看起来好像粉复杂, 但其实我们可以简单把其理解为, 一条执行绪就是一系列做事的行程。
所以当我们定义一支程序的执行绪有两条, 想要完成的任务是让使用者可以线上与人进行格斗比赛, 则在概念上可以设计如下两条thread
我们可以很明显的发现, 若我们的程序是先执行第一件事再执行第二件事, 再回头执行第一件事, 再做第二件事, 即是以如下写法
void enemyHit(){
//监听远端服务器指令来得知敌方操作, 接着同步本地软件内敌方脚色的行为 , 使本地玩家得知对方的行动
}
void weHit(){
//监听本地玩家的操作, 接着同步本地软件内我方脚色的行为, 最後上传本地脚色的操作到远端服务器, 使敌方玩家可以知道我方脚色的操作。
}
while(1){
enemyHit();
weHit()
}
就会发生整场格斗都是你一拳我一拳, 我一拳你一拳的回合制战斗.
那该怎麽做才可以让玩家来场酣畅淋漓的实时战斗呢? 问题的答案很简单
只要 : 两条流程同时做就可以啦 , 所以程序同时在接收敌方的操作, 也在上传己方的操作,这时我们就可以说这支程序是个双线程(thread)的程序。
注 : 以上内容不包含完整知识, 为了简化概念舍弃了许多内容, 不过用来理解下方教学已经够用了。
所谓同步非同步语法, 即为一种方式可以定义不同条流程分别要做甚麽事情, 并且设定两条流程的沟通规则, 包含两条流程谁要先完成谁要後完成, 还是同时做(实务上因为CPU一次只能做一件事, 所以同时做会是以快速的交替做来完成), 都可以在这边定义。
以下会分成4阶段,
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
//创建4条子线程
Task subThread1 = new Task(() =>
{
//这里可以填入一系列要让该线程做的事
Thread.Sleep(1000);
Console.WriteLine("I am subThread1!");
});
Task subThread2 = new Task(() =>
{
//这里可以填入一系列要让该线程做的事
Thread.Sleep(1000);
Console.WriteLine("I am subThread2!");
});
Task subThread3 = new Task(() =>
{
//这里可以填入一系列要让该线程做的事
Thread.Sleep(1000);
Console.WriteLine("I am subThread3!");
});
Task subThread4 = new Task(() =>
{
//这里可以填入一系列要让该线程做的事
Thread.Sleep(1000);
Console.WriteLine("I am subThread4!");
});
// 让线程沟通
// 让2条线程开始跑
subThread2.Start();
subThread1.Start();
// GetAwaiter() : 等待完成, OnCompleted() : 线程完成後要做的事
subThread1.GetAwaiter().OnCompleted(()=> {
// 线程完成後要做的事
// 让2条线程开始跑, 当第一条线程跑完
subThread4.Start();
subThread3.Start();
});
// GetAwaiter() : 等待完成, GetResult() : 取得结果
// 实际写法范例 result = subThread2.GetAwaiter().GetResult(); (这里只是因为没有回传才这样写)
subThread2.GetAwaiter().GetResult();
// 等待线程完成才继续往下走
subThread3.Wait();
subThread4.Wait();
}
}
}
若是实际运行上述代码於本地会发现每次结果相异, 以下解释相关重点资讯。
// 此处定义了如何定义新线程(一条独立的做事流程), 但其并没有开始运行
Task subThread1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine("I am subThread1!");
});
// 下达指令, 创建线程开始运行, 所以此刻程序包含该程序的主线程, 总共有三条独立的做事流程在进行。
subThread2.Start();
subThread1.Start();
// subThread1线程完成後创建subThread3 subThread4线程开始运行
subThread1.GetAwaiter().OnCompleted(()=> {
subThread4.Start();
subThread3.Start();
});
// 等待 subThread3完成主线程才继续往下
subThread3.Wait();
范例输出 :
结果不同的原因, 下方的沟通只定义了等到XXX完成才继续, 但在XXX.Start()後, 一堆线程早就同时在运行了, 有可能主线程还没运行到等待那行, XXX就已经完成了。
与第一部分程序码效果基本一致, 可以自行对照
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
//Main 为C#进入点, 不可为非同步函式, 所以用传统语法对我们的同步函数进行包装
int n = main().GetAwaiter().GetResult();
Console.WriteLine(n);
}
// 同步函数会回传、创建且运行一个线程, return後方的回传值会直接包在Task里面
// 所以若是 return後面是一个字串, 则实际传出的就是一个 Task<string>
static async Task createTask(int threadNum)
{
//这里可以填入一系列要让该线程做的事
// await表示该线程完成再继续执行, Task.Delay表示创建一个线程其行为为等待XXX毫秒
await Task.Delay(1000);
Console.WriteLine($"I am subThread{threadNum}!");
return;
}
static async Task<int> main()
{
//创建且执行两条新线程
Task subThread1 = createTask(1);
Task subThread2 = createTask(2);
// 等待某一线程完成
await subThread1;
//创建且执行两条新线程
Task subThread3 = createTask(3);
Task subThread4 = createTask(4);
// 等待以下线程完成後 return
await subThread2;
await subThread3;
await subThread4;
return 1;
}
}
}
以下为call API常用到的程序码, 引用组件, 寄送http request, 由於该组件寄request的方法为创建一个新线程来寄送, 所以须利用本篇教学的内容来完成撰写。
传统语法
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace general
{
class Program
{
static HttpClient client = new HttpClient();
static void Main(string[] args)
{
//读取参数 非本教学重点
StreamReader r = new StreamReader("xxx.json");
string jsonString = r.ReadToEnd();
string res = PostRequest("API", jsonString);
}
public static string PostRequest(string URI, string PostParams)
{
//设定API 非本教学重点
client.BaseAddress = new Uri("http://XXX");
client.DefaultRequestHeaders.Add("sat", "1234");
client.DefaultRequestHeaders.Add("sid", "1234");
client.DefaultRequestHeaders.Add("code", "");
client.Timeout = TimeSpan.FromSeconds(30);
//创建新线程, 实际利用组件寄request, 且等待http response回传才会继续往下走。(该组件该函数会自行创建线程且运行)
HttpResponseMessage response = client.PostAsync(URI, new StringContent(PostParams)).GetAwaiter().GetResult();
//创建新线程, 利用组件解读response, 且等待解读完成回传後才继续运行。(该组件该函数会自行创建线程且运行)
string content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return content;
}
}
}
async await
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace AsyncAwait
{
class Program
{
static HttpClient client = new HttpClient();
static void Main(string[] args)
{
main().GetAwaiter().GetResult();
}
static async Task main()
{
//读取参数 非本教学重点
StreamReader r = new StreamReader("xxx.json");
string jsonString = r.ReadToEnd();
//创建新线程来完成寄request, 且等待回传才会继续往下走。
string res = await PostRequest("API", jsonString);
//do things about res
return;
}
public static async Task<string> PostRequest(string URI, string PostParams)
{
//设定API 非本教学重点
client.BaseAddress = new Uri("http://XXX");
client.DefaultRequestHeaders.Add("sat", "1234");
client.DefaultRequestHeaders.Add("sid", "1234");
client.DefaultRequestHeaders.Add("code", "");
client.Timeout = TimeSpan.FromSeconds(30);
//创建新线程, 实际利用组件寄request, 且等待http response回传才会继续往下走。
HttpResponseMessage response = await client.PostAsync(URI, new StringContent(PostParams));
//创建新线程, 利用组件解读response, 且等待解读完成回传後才继续运行。
string content = await response.Content.ReadAsStringAsync();
//回传解读内容给正在等待的父线程
return content;
}
}
}
本程序的目的为把pathOfFolder资料夹下从0.jpg~99.jpg的档案名变更成0_new.jpg~99_new.jpg
其中利用把整个操作打包成一个线程, 再把线程复制100次, 使其可以100件事同时做(实际上基於底层原理不会如此理想)。
whenAll的功能是同时执行其传入作为参数的所有线程, 且传入参数须为List型别。
private void whenAllDemo()
{
List<Task> taskList = new List<Task>();
for(int i = 0; i < 100; i++)
{
string sourceName = i.toString();
string disName = i.toString() + "_new";
taskList.Add(Task.Run(() => {
try
{
File.Move($"{pathOfFolder}\\{sourceName}.jpg" , $"{pathOfFolder}\\{disName}.jpg");
}
catch (Exception err)
{
MessageBox.Show(err.Message);
throw;
}
}));
}
Task allTask = Task.WhenAll(taskList);
try
{
allTask.Wait();
}
catch { }
if (allTask.Status == TaskStatus.RanToCompletion)
MessageBox.Show("success!");
else if (allTask.Status == TaskStatus.Faulted)
MessageBox.Show("something wrong");
}
<<: CMoney软件工程师战斗营_心得感想_Week 20
# -*- coding: utf-8 -*- import numpy as np import ...
前言 昨天我们介绍过如何使用 React.memo 与 useCallback 来做效能优化,而 u...
由於组件可以在不同的context中使用,有几种方法可以解决这个问题,官方连结。 1.一次性情况的特...
今天要盖出阿嬷家!让小红帽走进阿嬷家,找到阿嬷。 1.新建场景 右键 > Create >...
目前先和大家介绍一些基本的应用 大概Day15~16後会开始结合GetX 一般比较两个 基本型别是否...