C# 多线程九 任务Task的简单理解与运用三

目录

方法:

ConfigureAwait(Boolean)   

ContinueWith

Delay

Dispose()

Dispose(Boolean)

说明:

FromCanceled(CancellationToken)     FromCanceled(CancellationToken)     

FromResult(TResult)   

FromException(Exception)     FromException(Exception) 

GetAwaiter()   

RunSynchronously()     RunSynchronously(TaskScheduler)     

Wait()     

WaitAll

WaitAny

WhenAll    

WhenAny

Yield()     

异步Async 和 Await


上篇:

C# 多线程八 任务Task的简单理解与运用二

 接着上篇,我们这篇主要说一下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方法说明中大都用过,此处不再赘述

如果有不对的地方希望能指出来 感激不尽。

另外,不熟悉的代码一定要写一下加深记忆 只用看的记不了太久。

猜你喜欢

转载自blog.csdn.net/SmillCool/article/details/127359887