英文旅游网站建设,网站做强制访问控制,页面设置标签wordpress,wordpress对的密码无法登录前言
在文章《async和await》中#xff0c;我们观察到了一下客观的规律#xff0c;但是没有讲到本质#xff0c;而且还遗留了一个问题:
这篇文章中#xff0c;我们继续看看这个问题如何解决! 我们再看看之前写的代码#xff1a;
static public void TestWait2()
{var t…前言
在文章《async和await》中我们观察到了一下客观的规律但是没有讲到本质而且还遗留了一个问题:
这篇文章中我们继续看看这个问题如何解决! 我们再看看之前写的代码
static public void TestWait2()
{var t Task.Factory.StartNew(async () {Console.WriteLine(Start);await Task.Delay(3000);Console.WriteLine(Done);});t.Wait();Console.WriteLine(All done);}static public void TestWait3()
{var t Task.Run(async () {Console.WriteLine(Start);await Task.Delay(3000);Console.WriteLine(Done);});t.Wait();Console.WriteLine(All done);
}当时问题是为啥 Task.Factory.StartNew 可以看到异步效果而Task.Run中却是同步效果。 那其实是因为Task.Factory.StartNew 返回的 t.Wait(); 它没卡住主线程而Task.Run的 t.Wait();它卡住了。
那为啥Task.Factory.StartNew没卡住呢 这是应为 Task.Factory.StartNew 返回的变量 t 他是Task Task 类型
如果Task.Run 返回的是Task类型如果我们改成Task.Factory.StartNew那么它 返回的类型就是TaskTask int
在.Net4.0中提供一个Unwrap方法用于将TaskTask int解为Task int类型所以如果代码改为
static public async void Factory嵌套死等()
{Console.WriteLine($Factory不嵌套死等{getID()});var t Task.Factory.StartNew(async() {Console.WriteLine($Start{getID()});await Task.Delay (1000);Console.WriteLine($Done{getID()});}).Unwrap();t.Wait();Console.WriteLine($All done{getID()});
}那么此时 t.Wait(); 也能卡死主线程。 其实Task.Run.net4.5引入 是在 Task.Factory.StartNew.net4.0引入 之后出现的Task.Run是为了简化Task.Factory.StartNew的使用。 t.Wait() 和 await t;
现在我从另一个角度分析问题。 使用 Task.Run能不能达到异步的效果 答案是肯定的 不过我们此时不应该使用 t.Wait(); 而是应该是 await t;
static public async void Run嵌套Await()
{Console.WriteLine($Run嵌套Await{getID()});var t Task.Run(async () {Console.WriteLine($Start{getID()});await Task.Delay(1000);Console.WriteLine($Done{getID()});});await t;Console.WriteLine($All done{getID()});
}这样的话就实现了异步效果。
await 是如何实现异步的
这里我们可以进一步分析一下。 “1” 是主线程的ID “5” 是 task 启的子线程 ID。 我发现All done 在 Done 后面执行的这是应为 await t; 把主线程遣返了 而await t; 之后的代码也就是All done 这句话的打印是由子线程5接着完成的。
整个流程是这样的当编译时编译器看到了函数使用了 async 关键字那么整个函数将被转换为一个带有状态机的函数反编译后发现函数名称变为MoveNext。
当主线程执行到子函数时遇到 await 那么此时 主线程就会返回跳出整个子函数去执行下一个函数MoveNext呢就会切换状态机由于状态机已经切换下次MoveNext在被调用时就会从await 处向下执行。 不过从现象看await 之后的代码不是主线程调用了而是Task的子线程。子线程会再次调用MoveNext并且进入一个新的状态机。 这里就有一个结论当主线程进入一个子函数遇到await机会从函数直接返回函数中以下的代码交给新的子线程执行。 为了证明一这一点我又写了一个程序
static public async Task AsyncInvoke()
{await Task.Run(() {Console.WriteLine($This is 1 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);});Console.WriteLine($1{getID()});await Task.Run(() {Console.WriteLine($This is 2 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);});Console.WriteLine($2{getID()});await Task.Run(() {Console.WriteLine($This is 3 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);});Console.WriteLine($3{getID()});await Task.Run(() {Console.WriteLine($This is 4 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);});Console.WriteLine($4{getID()});
}
执行效果如下 你会发现不过一个函数里面有多少个await 主线程遇到一个await就返回了就跳出这个函数去执行其他的函数了。 函数剩下的await 后面的都是由子线程完成的多个await 只是多个几个状态机而已。 所以在一个函数中如果有个多个await 除了第一个后面的都和主线程无关。
这里又出现了一个新的问题为啥后面的线程ID都是5这个其实不一定的我重新跑了一次 这次发现出现了两个子线程号 3 和 5这是应为 Task 背后有个 线程池。Task 被翻译为任务单纯的线程是指的Thread。 Task 启动后使用哪个线程是由背后的线程池提供而这个线程池是由.net进行维护。包括回调什么时候发生都是由线程池中的一个线程通知Task对象await 操作符 其实是 调用 Task对象的 ContinueWith所以上面这段代码也可以这么写
/// summary
/// 回调式写法
/// /summary
public void TaskInvokeContinue()
{Task.Run(() {Console.WriteLine($This is 1 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);}).ContinueWith(t {Console.WriteLine($This is 2 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);}).ContinueWith(t {Console.WriteLine($This is 3 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);}).ContinueWith(t {Console.WriteLine($This is 4 ManagedThreadId{Thread.CurrentThread.ManagedThreadId.ToString(00)});Thread.Sleep(1000);});//不太爽---nodejs---回调式写法嵌套是要疯掉
}这就进一步体现了 await 用同步的方式写异步的代码。 能实现这个的原因就是函数已经被改造成一个状态机了。
到这里我就把上次坑给填上了下次我们在一起掰扯掰扯Task的一些有意思的用法。
小结
我觉得最重要的一点就是 主线程遇到一个await就返回了如何理解这个返回 返回就是跳出这个函数和这个函数没有半毛钱关系了去执行其他下面的函数了。 该函数剩下的await后面的部分 都是由线程池中的子线程完成的 理解这一点有助于我们对异步代码的编写
2023年7月29日 更新 一次Debug的分享
昨天才写完这篇文章今天就发现之前的写的一段代码有问题。没想到这么快就用上了~~笑哭
程序大概是这样的。我有一个主线程里面有两个函数A和BA和B实现了 async await 。 A和B里有一句 await tcpcli.SendAsync(str) 这句异步代码, 大致代码如下
while(true)
{await A(){....bool b await tcpcli.SendAsync(str);}await B(){....await tcpcli.SendAsync(str);}
}正常情况下这样没啥问题。程序都是正常跑。但是当Tcp服务那边反应延时的时候就会出问题。 运行到 bool b await tcpcli.SendAsync(str); 时按照之前的结论主线程都是直接返回的就会直接执行B 然后再接着执行A但是如果bool b await tcpcli.SendAsync(str); 依然还在等待之线程还是会返回的 此时会再次开一个新的线程导致多个线程并发但是我这里其他逻辑并发的话是会有问题的比如写Modbus的一个寄存器。 所以一旦 tcpcli.SendAsync(str)卡住了逻辑就出问题了
既然逻辑不能并发我当时为啥不直接用同步的方式呢其实原因是当时我不知道如何用同步的方式获取返回值。 我当时 调用 tcpcli.SendAsync(str).Wait(); 时发现这个Wait()返回值是空但是我又需要返回值所以就用了 bool b await tcpcli.SendAsync(str); 那其实如果想用同步的方式获取返回值应该使用 bool b tcpcli.SendAsync(str).GetAwaiter().GetResult(); 所以最后改程序为
while(true)
{await A(){....bool b tcpcli.SendAsync(str).GetAwaiter().GetResult();}await B(){....tcpcli.SendAsync(str).Wait();}
}