0%

C#开发相关思考

大致内容:

C#中的异步、同步、委托、事件、回调的一些用例,也算不上什么高级的开发技巧,熟练使用的话开发能减少不少的工作量。当然除了C#以外、其他语言的思想大致也是这样。

异步、同步

在一定有限的时间里,可能要执行一些无法估计时间的代码,比如网络任务、UI加载等,但由于这部分任务会占用主线程一定的时间,这段时间里用户可能就面对的是一个无响应的界面(因为相关进程都被卡死了,都在等这部分任务的执行)。这时候一个很自然而然的想法就应运而生:有没有办法,让这部分任务在后台进行,得到结果后再传入用户的UI界面?这个 分开任务执行流程 ,最后 再并入主线程 的办法就叫 异步。

通俗来讲就是:在一定的时间(生命周期)内,做多个事情。Task类就是其中的一个容器,这个容器约定好了返回的类型,告知程序这个异步最后期望的运行是个什么样的结果。

1
2
3
4
5
6
7
8
9
10
void Test0(){}; //普通的函数,返回空值、也不用规定Task类型
async void Test1(){}; //异步的函数,返回空值
int Test2(){}; //返回整数
async Task<int> Test3(){}; //约定好返回整数的异步函数

async void DoSomething(){
Task<int> Result = Test3(); //调用异步执行的方法,此时函数不会等待线程的执行
int _Result = Result.result; //获取执行结果,此时变量通过回调等待异步线程的执行结束
int _result = await Test3(); //等待异步执行的方法,此时函数会等待线程的执行
}

大致就这样,当我们执行async函数的时候,程序会创建一个新的线程,主线程不会等待新线程的执行,就继续执行之后的流程,这样就避免了线程阻塞的问题。

新的问题随之而来:那么既然无法估计分支线程的执行时间,又没法在一个同步的线程里调用异步线程,又该怎么在程序里写好异步执行的逻辑呢?毕竟如果只是简单点点点UI的话、直接单弄一个线程去管理相关的执行最后在回传显示逻辑就好,如果项目再复杂点的呢?相关示例也记不太清了。

委托、事件、回调

这里面真正重要且要理解的只有一个,就是 事件(event) 的核心思想:简单地说事件就像一个公共函数,当调用事件的时候,每一个订阅事件的对象都可以根据订阅的时间点去执行不同的逻辑。
比方说 某个角色 的数据更新了,但是我又不方便直接把角色的数据接到UI对象上,万一UI要改了,不显示这个数据了,那么除了Ui要再做一轮、角色的逻辑代码又得改一轮,这样就加深了工作量。
但是把数据改变这个逻辑通过回调进行监听传入事件,之后就好办了,当数据集改编的时候UI直接订阅这个事件,想取什么就用什么,直接同步执行更改显示,那么一听确实方便,当要改UI的时候,也不用动角色的代码,直接改个UI参数的事情。

而委托,就有点像是个接口、约定好可以传入的参数类型,告知事件(event): 什么样的对象会被接下来的 额外逻辑 代码继承,很拗口、这部分的逻辑确实有点难理解,放一段代码示例大概就能稍微体会这个解耦的思想了:事件只是一个提供公共函数的接口,供外部动态调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using System.Threading;

// 定义委托(如果使用非泛型EventHandler,需要定义)
//在这里,我们约定好后续的额外函数可以获取到事件内的什么信息
public delegate void TimerEventHandler(object sender, EventArgs e);

public class Clock //对象A,基于事件提供一个公共接口
{
// 基于委托定义事件
public event TimerEventHandler Timer;

// 触发事件的方法
public void RunClock()
{
while (true)
{
Thread.Sleep(1000);
// 触发事件
Timer?.Invoke(this, EventArgs.Empty);
}
}
}

public class Display //额外的逻辑片段,用于"续写"对象A的逻辑
{
// 事件处理方法
public void ShowTime(object sender, EventArgs e)//这些参数由对象A提供、在委托的时候已经约定好了
{
Console.WriteLine($"Current time: {DateTime.Now.ToLongTimeString()}");
}
}

class Program //对象B,动态调用对象A的接口
{
static void Main(string[] args)
{
Clock clock = new Clock();
Display display = new Display();
// 订阅事件
clock.Timer += display.ShowTime; //引用公共函数,每当对象A执行的时候,该逻辑都会被执行一遍
// 启动时钟
clock.RunClock();
}
}

因为笔者也没啥好展示的详细用法,大体上、这些概念的用途是这样子,想到什么再记什么了吧。