目录
FromCanceled(CancellationToken) FromCanceled(CancellationToken)
FromException(Exception) FromException(Exception)
RunSynchronously() RunSynchronously(TaskScheduler)
上篇:
接着上篇,我们这篇主要说一下Task的方法
前言:Task的方法说明基本都是从Task.Exception 属性 (System.Threading.Tasks) | Microsoft Learn拷贝下来的,只是加了自己的理解和测试代码以及打印来供大家更好的理解。
方法:
ConfigureAwait(Boolean)
配置用于等待此 Task的 awaiter。
没太懂 有兴趣的可以看下http:// https://www.cnblogs.com/xiaoxiaotank/p/13529413.html
我的大致理解是使用 await task.ConfigureAwait(false);可以实现异步,后续方法不用同步排队执行
展示一个winform的例子:
private void button2_Click(object sender, EventArgs e) {
Debug.Print($"创建task的线程(主线程):{Thread.CurrentThread.ManagedThreadId}");
Task task = Task.Run(() => {
Debug.Print($"主任务线程:{Thread.CurrentThread.ManagedThreadId} ");
});
task.ContinueWith((obj) => {
Debug.Print($"附加任务线程:{Thread.CurrentThread.ManagedThreadId} ");
});
ConfiguredTaskAwaitable taskAwaitable = task.ConfigureAwait(true);
taskAwaitable.GetAwaiter().OnCompleted(() => {
Debug.Print($"后续任务线程:{Thread.CurrentThread.ManagedThreadId} ");
});
}
打印:
我们可以看到当task.ConfigureAwait(true)的时候 taskAwaitable.GetAwaiter().OnCompleted持有的委托使用的是线程1 既是创建task的线程
下边是修改未false之后的
下边是修改未false之后的
private void button2_Click(object sender, EventArgs e) {
Debug.Print($"创建task的线程(主线程):{Thread.CurrentThread.ManagedThreadId}");
Task task = Task.Run(() => {
Debug.Print($"主任务线程:{Thread.CurrentThread.ManagedThreadId} ");
});
task.ContinueWith((obj) => {
Debug.Print($"附加任务线程:{Thread.CurrentThread.ManagedThreadId} ");
});
ConfiguredTaskAwaitable taskAwaitable = task.ConfigureAwait(false);
taskAwaitable.GetAwaiter().OnCompleted(() => {
Debug.Print($"后续任务线程:{Thread.CurrentThread.ManagedThreadId} ");
});
}
打印:
ContinueWith
创建一个在目标 Task 完成时接收调用方提供的状态信息并执行的延续任务。
可以指定在任务完成之后,应开始运行之后另一个特定任务
代码:
static void Main(string[] args) {
Task task = Task.Run(() => {
for(int i = 1; i < 100; i++)
Console.WriteLine($"{i} ");
});
Task<string> task1 = task.ContinueWith((obj) => {
return "开始执行2";
});
Console.WriteLine(task1.Result);
Console.ReadLine();
}
打印:
Delay
创建一个在指定时间后完成的任务。
代码:
static async void DoSomething1() {
Console.WriteLine("DoSomething1 进入线程" + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1000);
Console.WriteLine("DoSomething1 方法结束" + Thread.CurrentThread.ManagedThreadId);
}
static void Main(string[] args) {
DoSomething();
DoSomething1();
}
打印:
Dispose()
释放 Task 类的当前实例所使用的所有资源。
Dispose(Boolean)
释放 Task,同时释放其所有非托管资源。
(继承自 Object)
说明:
Task.FromResult/Task.CompletedTask/Task.FromException/Task.FromCanceled
这几个最初我也不太懂,然后我在问答上边提问了一下 有个老哥的解释大致是这个意思:其实这几个都是一个意思,就是从池里产生一个具备对应状态的Task出来,他不代表task执行,而是代表执行结果
例如:
Task.FromCanceled的正确执行语句为
if(token.IsCancellationRequested)
Task.FromCanceled(token) //取消了情况他会正常执行,没有取消的情况他直接异常
如果令牌取消了,那么产生一个已经取消的task,至于这个task有啥用,是用来 产生结果回调通知给外面的下面我们再看这几个方法可能好理解一些
FromCanceled(CancellationToken)
FromCanceled<TResult>(CancellationToken)
创建 Task/Task<TResult>,它因指定的取消标记进行的取消操作而完成。
FromResult<TResult>(TResult)
创建指定结果的、成功完成的 Task<TResult>。
以同步的方法返回异步的值
FromException(Exception)
FromException<TResult>(Exception)
创建 Task/Task<TResult>,它在完成后出现指定的异常。
代码:
static void Main(string[] args) {
DoSth();
Console.ReadKey();
}
static void DoSth() {
Task<string> task = Task.Run(() => {
return Task.FromException<string>(new Exception("我不行了,我异常了"));
});
Console.WriteLine(task.Result);
}
打印:
GetAwaiter()
获取用于等待此 Task 的 awaiter。
.Net注解:此方法适用于编译器使用,而不是直接在代码中使用。
RunSynchronously()
RunSynchronously(TaskScheduler)
对提供的 Task 同步运行 TaskScheduler。
代码:
static void Main(string[] args) {
Task task = new Task(() => {
Console.WriteLine($"1 :{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"2 :{Thread.CurrentThread.ManagedThreadId}");
});
task.RunSynchronously();
Console.WriteLine($"3 :{Thread.CurrentThread.ManagedThreadId}");
Console.ReadKey();
}
打印:
由打印可以看出 一个线程跑的
Wait()
等待 Task 完成执行过程。
多种方式
1.在指定的毫秒数内完成执行Wait(int millisecondsTimeout);
2.在指定的时间间隔内完成执行。Wait(TimeSpan timeout);
3.完成执行过程。 如果在任务完成之前取消标记已取消,等待将终止 Wait(CancellationToken cancellationToken);
4.完成执行过程。 Wait();
5.完成执行过程。 如果在任务完成之前超时间隔结束或取消标记已取消,等待将终止Wait(int millisecondsTimeout, CancellationToken cancellationToken);
大差不差 写一个第三种的把
1.不取消
代码:
using(CancellationTokenSource tokenSource = new CancellationTokenSource()) {
Task task = new Task(() => {
Console.WriteLine($"1 :{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"2 :{Thread.CurrentThread.ManagedThreadId}");
}, tokenSource.Token);
task.Start();
task.Wait(tokenSource.Token);
Console.WriteLine($"3 :{Thread.CurrentThread.ManagedThreadId}");
}
打印:
2.取消:
static void Main(string[] args) {
using(CancellationTokenSource tokenSource = new CancellationTokenSource()) {
Task task = new Task(() => {
Console.WriteLine($"1 :{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"2 :{Thread.CurrentThread.ManagedThreadId}");
}, tokenSource.Token);
task.Start();
Task.Run(() => {
Thread.Sleep(1000);
Console.WriteLine($"取消任务task的执行");
tokenSource.Cancel();
});
try {
task.Wait(tokenSource.Token);
} catch(Exception ex) {
Console.WriteLine(ex);
} finally {
Console.WriteLine($"3 :{Thread.CurrentThread.ManagedThreadId}");
}
}
Console.ReadKey();
}
打印:
可以看出任务取消之前 一直在等待 直到任务取消后 等待结束 抛出异常
WaitAll
等待提供的所有 Task 对象完成执行过程。
等待传入多个任务结束
代码:
static void Main(string[] args) {
using(CancellationTokenSource cts = new CancellationTokenSource()) {
CancellationToken token = cts.Token;
Task[] taskList = new Task[10];
for(int i = 0; i < 10; i++) {
taskList[i] = Task.Factory.StartNew((obj) => {
Thread.Sleep((int)obj * 100);
Console.WriteLine($"{(int)obj} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
}, i, token);
}
Console.WriteLine($"等待前线程 线程ID:{Thread.CurrentThread.ManagedThreadId}");
Task.WaitAll(taskList);
Console.WriteLine($"任一任务完成 线程ID:{Thread.CurrentThread.ManagedThreadId}");
}
Console.ReadKey();
}
打印:
WaitAny
等待提供的任一 Task 对象完成执行过程。
等待传入多个任务中的其中一个结束
代码:
static void Main(string[] args) {
using(CancellationTokenSource cts = new CancellationTokenSource()) {
CancellationToken token = cts.Token;
Task[] taskList = new Task[10];
for(int i = 0; i < 10; i++) {
taskList[i] = Task.Factory.StartNew((obj) => {
Thread.Sleep((int)obj * 100);
Console.WriteLine($"{(int)obj} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
}, i, token);
}
Task.WaitAny(taskList);
Console.WriteLine($"任一任务完成 线程ID:{Thread.CurrentThread.ManagedThreadId}");
}
Console.ReadKey();
}
打印:
WhenAll
创建一个任务,该任务将在可枚举集合中的所有 Task 对象都已完成时完成。
代码:
static void Main(string[] args) {
string[] strs = DoSth().Result;
for(int i = 0; i < strs.Length; i++)
Console.WriteLine($"{strs[i]}");
Console.ReadKey();
}
static async Task<string[]> DoSth() {
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task<string>[] taskList = new Task<string>[10];
for(int i = 0; i < 10; i++) {
taskList[i] = Task.Factory.StartNew<string>((obj) => {
Thread.Sleep((int)obj * 100);
if(!token.IsCancellationRequested)
Console.WriteLine($"{(int)obj} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
return ((int)obj).ToString();
}, i, token);
}
Task<string[]> task1;
Console.WriteLine($"等待前线程ID:{Thread.CurrentThread.ManagedThreadId}");
await(task1 = Task.WhenAll(taskList));
Console.WriteLine($"任务完成 线程ID:{Thread.CurrentThread.ManagedThreadId}");
cts.Dispose();
return task1.Result;
}
打印:
通过打印可以看出 使用 await(task1 = Task.WhenAll(taskList)); 会等待所有线程完成才接着往下运行,
我们结合WaitAll来看 等待前线程ID和等待后线程ID很轻易的发现
WaitAll是同步的通过阻塞主线程的方式来等待的
WhenAll是异步的不阻塞主线程
WhenAny
任何提供的任务已完成时,创建将完成的任务。
代码:
static void Main(string[] args) {
Console.WriteLine($"{DoSth().Result}");
Console.ReadKey();
}
static async Task<string> DoSth() {
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task<string>[] taskList = new Task<string>[10];
for(int i = 0; i < 10; i++) {
taskList[i] = Task.Factory.StartNew<string>((obj) => {
Thread.Sleep((int)obj * 100);
if(!token.IsCancellationRequested)
Console.WriteLine($"{(int)obj} 线程ID:{Thread.CurrentThread.ManagedThreadId}");
return ((int)obj).ToString();
}, i, token);
}
Console.WriteLine($"等待前线程ID:{Thread.CurrentThread.ManagedThreadId}");
Task<string> task1 = await Task.WhenAny(taskList);
Console.WriteLine($"任务完成 线程ID:{Thread.CurrentThread.ManagedThreadId}");
cts.Dispose();
return task1.Result;
}
打印:
Yield()
创建异步产生当前上下文的等待任务。
代码:
static void Main(string[] args) {
DoSth();
Console.WriteLine($"让我运行一下吧 线程ID : {Thread.CurrentThread.ManagedThreadId}");
Console.ReadKey();
}
static async void DoSth() {
int i = 0;
while(i < 10000) {
i++;
Console.Write($" 当前线程ID : {Thread.CurrentThread.ManagedThreadId}");
if(i % 100 == 0)
await Task.Yield();
}
}
打印:
从打印可以看出当执行await Task.Yield();时,相当于把当前线程从繁忙的工作中抽出来透个气(执行其他工作,所以也不算透气啦),让其他线程顶上来
为什么会这样?
因为Task.Yield会创建一个空的已经完成的Task然后借助Await的线程切换能力实现这种线程切换
那究竟为何需要这种操作呢?
可以将不太重要的比较耗时的操作放在新的线程(重新排队从线程池中申请到的线程)中执行
异步Async 和 Await
关于线程的同步我们之前已经说了好多,比如原子操作Interlocked 管程Monitor 互斥Mutex 事件AutoResetEvent/ManualResetEvent
今天我们了解一下线程的异步操作Async 和 Await
Async 和 Await 我们上边的例子也多次使用过了
并且通过Task.Yield也了解到Await具备线程切换能力
同时 如果你有跟着写一下代码的话也应该发现了Await的使用区间必须时Async修饰的域内
没有Async的Await会报错
没有Await的Async是一个同步方法
使用Async修饰的方法可以返回Void,Task,Task<TResult>由方法ConfigureAwait可以知道Await不但可以修饰Task,Task<TResult>还可以修饰ConfiguredTaskAwaitable(有兴趣的可以了解一下await的修饰有什么要求,此处不再赘述)
写一个例子:
代码:
static void Main(string[] args) {
DoSth();
DoSth2();
Console.WriteLine($"主线程ID:{Thread.CurrentThread.ManagedThreadId}");
Console.ReadKey();
}
static async void DoSth() {
await Task.Run(() => {
Thread.Sleep(2000);
Console.WriteLine($"DoSth Task 运行 线程ID:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine($"DoSth线程ID:{Thread.CurrentThread.ManagedThreadId}");
}
static async void DoSth2() {
await Task.Factory.StartNew(
async() => {
await Task.Delay(1000);
Console.WriteLine($"DoSth2 Task 运行 线程ID:{Thread.CurrentThread.ManagedThreadId}");
}
);
Console.WriteLine($"DoSth2线程ID:{Thread.CurrentThread.ManagedThreadId}");
}
打印:
其中DoSth()是一层异步 可以看到DoSth()的运行并不会阻塞主线程
DoSth2是两层异步 可以看到Task内部的await并不会阻塞外部的DoSth2线程ID的打印。
其中还有很多Async/Await和Task方法的联合使用,上边Task方法说明中大都用过,此处不再赘述
如果有不对的地方希望能指出来 感激不尽。
另外,不熟悉的代码一定要写一下加深记忆 只用看的记不了太久。