栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

Task和Thread的关系

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Task和Thread的关系

之前一直有个疑问Thread.CurrentThread.ManagedThreadId和Task.CurrentId到底有什么关系。出于这个好奇,动手写了一段程序来验证它们到底有没有关系。同时看看被Async/await修饰的方法跟没有被Async/await修饰的方法的不同之处。

在MSDN中Async/await的解释:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/

首先贴出被Async/await修饰的方法的测试代码。
 class Program
    {
        static void Main(string[] args)
        {
            Asy_Class asy = new Asy_Class();
            DateTime data = DateTime.Now;
            for (int i = 0; i < 50; i++)
            {
                Task index = method(asy, data, i);
                Console.WriteLine("方法{0} 调用完成 此任务的ID->{1}, 托管线程->{2}", i, index.Id, Thread.CurrentThread.ManagedThreadId);
            }
            Console.ReadKey();
        }
        public static async Task method(Asy_Class asy, DateTime data, int i)
        {
            int r = await asy.MethodA(data, i);
            Console.WriteLine("方法{0} r = {2} Async被执行,托管线程->{1}", i, Thread.CurrentThread.ManagedThreadId, r);
            return r;
        }
    }

    class Asy_Class
    {
        public Task MethodA(DateTime time, int i)
        {
            Task r = Task.Run(() =>
            {
                Console.WriteLine("方法{0} 任务ID->{2} Task被执行,托管线程->{1}", i, Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
                Thread.Sleep(100);
                return i;
            });
            Console.WriteLine("方法{0} 执行完毕,任务的ID->{1},托管线程->{2}", i, r.Id, Thread.CurrentThread.ManagedThreadId);

            if (i == 49)
                Console.WriteLine("用时{0}", (DateTime.Now - time).TotalMilliseconds);
            return r;
        }   
    }

把输出结果也贴出来:

输出结果:
方法0 执行完毕,任务的ID->1,托管线程->1
方法0 任务ID->1 Task被执行,托管线程->3
方法0 调用完成 此任务的ID->2, 托管线程->1
方法1 执行完毕,任务的ID->3,托管线程->1
方法1 调用完成 此任务的ID->4, 托管线程->1
方法1 任务ID->3 Task被执行,托管线程->4
方法2 执行完毕,任务的ID->5,托管线程->1
方法2 任务ID->5 Task被执行,托管线程->5
方法2 调用完成 此任务的ID->6, 托管线程->1
方法3 执行完毕,任务的ID->7,托管线程->1
方法3 任务ID->7 Task被执行,托管线程->6
方法3 调用完成 此任务的ID->8, 托管线程->1
方法4 任务ID->9 Task被执行,托管线程->7
方法4 执行完毕,任务的ID->9,托管线程->1
方法4 调用完成 此任务的ID->10, 托管线程->1
方法5 执行完毕,任务的ID->11,托管线程->1
方法5 调用完成 此任务的ID->12, 托管线程->1
方法6 执行完毕,任务的ID->13,托管线程->1
方法5 任务ID->11 Task被执行,托管线程->9
方法6 调用完成 此任务的ID->14, 托管线程->1
方法7 执行完毕,任务的ID->15,托管线程->1
方法7 调用完成 此任务的ID->16, 托管线程->1
方法8 执行完毕,任务的ID->17,托管线程->1
方法8 调用完成 此任务的ID->18, 托管线程->1
方法6 任务ID->13 Task被执行,托管线程->10
方法9 执行完毕,任务的ID->19,托管线程->1
方法9 调用完成 此任务的ID->20, 托管线程->1
方法10 执行完毕,任务的ID->21,托管线程->1
方法10 调用完成 此任务的ID->22, 托管线程->1
方法11 执行完毕,任务的ID->23,托管线程->1
方法11 调用完成 此任务的ID->24, 托管线程->1
方法12 执行完毕,任务的ID->25,托管线程->1
方法12 调用完成 此任务的ID->26, 托管线程->1
方法7 任务ID->15 Task被执行,托管线程->8
方法13 执行完毕,任务的ID->27,托管线程->1
方法13 调用完成 此任务的ID->28, 托管线程->1
方法14 执行完毕,任务的ID->29,托管线程->1
方法14 调用完成 此任务的ID->30, 托管线程->1
方法15 执行完毕,任务的ID->31,托管线程->1
方法15 调用完成 此任务的ID->32, 托管线程->1
方法16 执行完毕,任务的ID->33,托管线程->1
方法16 调用完成 此任务的ID->34, 托管线程->1
方法17 执行完毕,任务的ID->35,托管线程->1
方法17 调用完成 此任务的ID->36, 托管线程->1
方法18 执行完毕,任务的ID->37,托管线程->1
方法18 调用完成 此任务的ID->38, 托管线程->1
方法19 执行完毕,任务的ID->39,托管线程->1
方法19 调用完成 此任务的ID->40, 托管线程->1
方法20 执行完毕,任务的ID->41,托管线程->1
方法20 调用完成 此任务的ID->42, 托管线程->1
方法21 执行完毕,任务的ID->43,托管线程->1
方法21 调用完成 此任务的ID->44, 托管线程->1
方法22 执行完毕,任务的ID->45,托管线程->1
方法22 调用完成 此任务的ID->46, 托管线程->1
方法23 执行完毕,任务的ID->47,托管线程->1
方法23 调用完成 此任务的ID->48, 托管线程->1
方法24 执行完毕,任务的ID->49,托管线程->1
方法24 调用完成 此任务的ID->50, 托管线程->1
方法25 执行完毕,任务的ID->51,托管线程->1
方法25 调用完成 此任务的ID->52, 托管线程->1
方法26 执行完毕,任务的ID->53,托管线程->1
方法26 调用完成 此任务的ID->54, 托管线程->1
方法27 执行完毕,任务的ID->55,托管线程->1
方法27 调用完成 此任务的ID->56, 托管线程->1
方法28 执行完毕,任务的ID->57,托管线程->1
方法28 调用完成 此任务的ID->58, 托管线程->1
方法29 执行完毕,任务的ID->59,托管线程->1
方法29 调用完成 此任务的ID->60, 托管线程->1
方法30 执行完毕,任务的ID->61,托管线程->1
方法30 调用完成 此任务的ID->62, 托管线程->1
方法31 执行完毕,任务的ID->63,托管线程->1
方法31 调用完成 此任务的ID->64, 托管线程->1
方法32 执行完毕,任务的ID->65,托管线程->1
方法32 调用完成 此任务的ID->66, 托管线程->1
方法33 执行完毕,任务的ID->67,托管线程->1
方法33 调用完成 此任务的ID->68, 托管线程->1
方法34 执行完毕,任务的ID->69,托管线程->1
方法34 调用完成 此任务的ID->70, 托管线程->1
方法35 执行完毕,任务的ID->71,托管线程->1
方法35 调用完成 此任务的ID->72, 托管线程->1
方法36 执行完毕,任务的ID->73,托管线程->1
方法36 调用完成 此任务的ID->74, 托管线程->1
方法37 执行完毕,任务的ID->75,托管线程->1
方法37 调用完成 此任务的ID->76, 托管线程->1
方法38 执行完毕,任务的ID->77,托管线程->1
方法38 调用完成 此任务的ID->78, 托管线程->1
方法39 执行完毕,任务的ID->79,托管线程->1
方法39 调用完成 此任务的ID->80, 托管线程->1
方法40 执行完毕,任务的ID->81,托管线程->1
方法40 调用完成 此任务的ID->82, 托管线程->1
方法41 执行完毕,任务的ID->83,托管线程->1
方法41 调用完成 此任务的ID->84, 托管线程->1
方法42 执行完毕,任务的ID->85,托管线程->1
方法42 调用完成 此任务的ID->86, 托管线程->1
方法43 执行完毕,任务的ID->87,托管线程->1
方法43 调用完成 此任务的ID->88, 托管线程->1
方法44 执行完毕,任务的ID->89,托管线程->1
方法44 调用完成 此任务的ID->90, 托管线程->1
方法45 执行完毕,任务的ID->91,托管线程->1
方法45 调用完成 此任务的ID->92, 托管线程->1
方法46 执行完毕,任务的ID->93,托管线程->1
方法46 调用完成 此任务的ID->94, 托管线程->1
方法47 执行完毕,任务的ID->95,托管线程->1
方法47 调用完成 此任务的ID->96, 托管线程->1
方法48 执行完毕,任务的ID->97,托管线程->1
方法48 调用完成 此任务的ID->98, 托管线程->1
方法49 执行完毕,任务的ID->99,托管线程->1
用时127.6601
方法49 调用完成 此任务的ID->100, 托管线程->1
方法0 r = 0 Async被执行,托管线程->3
方法8 任务ID->17 Task被执行,托管线程->3
方法2 r = 2 Async被执行,托管线程->5
方法1 r = 1 Async被执行,托管线程->4
方法9 任务ID->19 Task被执行,托管线程->5
方法10 任务ID->21 Task被执行,托管线程->4
方法3 r = 3 Async被执行,托管线程->6
方法11 任务ID->23 Task被执行,托管线程->6
方法5 r = 5 Async被执行,托管线程->9
方法4 r = 4 Async被执行,托管线程->7
方法13 任务ID->27 Task被执行,托管线程->7
方法12 任务ID->25 Task被执行,托管线程->9
方法6 r = 6 Async被执行,托管线程->10
方法14 任务ID->29 Task被执行,托管线程->10
方法7 r = 7 Async被执行,托管线程->8
方法15 任务ID->31 Task被执行,托管线程->8
方法8 r = 8 Async被执行,托管线程->3
方法16 任务ID->33 Task被执行,托管线程->3
方法10 r = 10 Async被执行,托管线程->4
方法17 任务ID->35 Task被执行,托管线程->4
方法12 r = 12 Async被执行,托管线程->9
方法18 任务ID->37 Task被执行,托管线程->9
方法13 r = 13 Async被执行,托管线程->7
方法14 r = 14 Async被执行,托管线程->10
方法19 任务ID->39 Task被执行,托管线程->7
方法20 任务ID->41 Task被执行,托管线程->10
方法11 r = 11 Async被执行,托管线程->6
方法21 任务ID->43 Task被执行,托管线程->6
方法15 r = 15 Async被执行,托管线程->8
方法9 r = 9 Async被执行,托管线程->5
方法23 任务ID->47 Task被执行,托管线程->5
方法22 任务ID->45 Task被执行,托管线程->8
方法16 r = 16 Async被执行,托管线程->3
方法24 任务ID->49 Task被执行,托管线程->3
方法17 r = 17 Async被执行,托管线程->4
方法25 任务ID->51 Task被执行,托管线程->4
方法18 r = 18 Async被执行,托管线程->9
方法26 任务ID->53 Task被执行,托管线程->9
方法19 r = 19 Async被执行,托管线程->7
方法27 任务ID->55 Task被执行,托管线程->7
方法20 r = 20 Async被执行,托管线程->10
方法28 任务ID->57 Task被执行,托管线程->10
方法21 r = 21 Async被执行,托管线程->6
方法29 任务ID->59 Task被执行,托管线程->6
方法22 r = 22 Async被执行,托管线程->8
方法30 任务ID->61 Task被执行,托管线程->8
方法23 r = 23 Async被执行,托管线程->5
方法31 任务ID->63 Task被执行,托管线程->5
方法24 r = 24 Async被执行,托管线程->3
方法32 任务ID->65 Task被执行,托管线程->3
方法25 r = 25 Async被执行,托管线程->4
方法33 任务ID->67 Task被执行,托管线程->4
方法26 r = 26 Async被执行,托管线程->9
方法34 任务ID->69 Task被执行,托管线程->9
方法27 r = 27 Async被执行,托管线程->7
方法35 任务ID->71 Task被执行,托管线程->7
方法28 r = 28 Async被执行,托管线程->10
方法36 任务ID->73 Task被执行,托管线程->10
方法29 r = 29 Async被执行,托管线程->6
方法37 任务ID->75 Task被执行,托管线程->6
方法30 r = 30 Async被执行,托管线程->8
方法31 r = 31 Async被执行,托管线程->5
方法39 任务ID->79 Task被执行,托管线程->5
方法38 任务ID->77 Task被执行,托管线程->8
方法32 r = 32 Async被执行,托管线程->3
方法40 任务ID->81 Task被执行,托管线程->3
方法33 r = 33 Async被执行,托管线程->4
方法41 任务ID->83 Task被执行,托管线程->4
方法34 r = 34 Async被执行,托管线程->9
方法42 任务ID->85 Task被执行,托管线程->9
方法35 r = 35 Async被执行,托管线程->7
方法43 任务ID->87 Task被执行,托管线程->7
方法36 r = 36 Async被执行,托管线程->10
方法44 任务ID->89 Task被执行,托管线程->10
方法37 r = 37 Async被执行,托管线程->6
方法45 任务ID->91 Task被执行,托管线程->6
方法39 r = 39 Async被执行,托管线程->5
方法46 任务ID->93 Task被执行,托管线程->5
方法38 r = 38 Async被执行,托管线程->8
方法47 任务ID->95 Task被执行,托管线程->8
方法41 r = 41 Async被执行,托管线程->4
方法48 任务ID->97 Task被执行,托管线程->4
方法46 r = 46 Async被执行,托管线程->5
方法42 r = 42 Async被执行,托管线程->9
方法45 r = 45 Async被执行,托管线程->6
方法49 任务ID->99 Task被执行,托管线程->5
方法43 r = 43 Async被执行,托管线程->7
方法40 r = 40 Async被执行,托管线程->3
方法44 r = 44 Async被执行,托管线程->10
方法47 r = 47 Async被执行,托管线程->8
方法49 r = 49 Async被执行,托管线程->5
方法48 r = 48 Async被执行,托管线程->4

接下来我们截取一段输出的结果,针对一两个结果来分析:

取出方法23和方法24执行的结果,执行顺序保持不变。

方法23 执行完毕,任务的ID->47,托管线程->1
方法23 调用完成 此任务的ID->48, 托管线程->1
方法24 执行完毕,任务的ID->49,托管线程->1
方法24 调用完成 此任务的ID->50, 托管线程->1
方法23 任务ID->47 Task被执行,托管线程->5
方法24 任务ID->49 Task被执行,托管线程->3
方法23  r = 23 Async被执行,托管线程->5
方法24  r = 24 Async被执行,托管线程->3

先看看被Async/await修饰的方法,在内存中的执行情况。

 根据调试现象来看,Thread.CurrentThread.ManagedThreadId和Task.CurrentId是没关系,

Task.Run()方法是把一个任务推给了线程池,当线程池中有空闲线程时则执行此Task任务(将在线程池上运行的指定排队任务)。Task.CurrentId是返回当前正在执行任务的ID,此ID是系统分配给当前正在执行的任务一个整数。

Thread.CurrentThread.ManagedThreadId属性是 获取当前托管线程的唯一标识符。不管是主线程还是线程池线程,都是用这个属性来查看方法在哪个线程中执行。

从上面输出结果可以看出来,不同任务可能推给线程池中的同一个线程去执行,前提是当前线程池中该线程执行完上一个任务后才能下一个任务。如:

方法23 任务ID->47 Task被执行,托管线程->5
方法23  r = 23 Async被执行,托管线程->5
方法31 任务ID->63 Task被执行,托管线程->5
方法31 r = 31 Async被执行,托管线程->5


我们再来看看没有被Async/await修饰的方法的测试代码,在内存中的执行情况。

需要修改method(Asy_Class asy, DateTime data, int i)方法。其他的不变。

class Program
    {
        static void Main(string[] args)
        {
            Asy_Class asy = new Asy_Class();
            DateTime data = DateTime.Now;
            for (int i = 0; i < 50; i++)
            {
                Task index = method(asy, data, i);
                Console.WriteLine("方法{0} 调用完成 此任务的ID->{1}, 托管线程->{2}", i, index.Id, Thread.CurrentThread.ManagedThreadId);
            }
            Console.ReadKey();
        }
        public static Task method(Asy_Class asy, DateTime data, int i)
        {
            Task r = asy.MethodA(data, i);
            Console.WriteLine("方法{0} 任务Id = {2} 被执行,托管线程->{1}", i, Thread.CurrentThread.ManagedThreadId, r.Id);
            return r;
        }
    }

    class Asy_Class
    {
        public Task MethodA(DateTime time, int i)
        {
            Task r = Task.Run(() =>
            {
                Console.WriteLine("方法{0} 任务ID->{2} Task被执行,托管线程->{1}", i, Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
                Thread.Sleep(100);
                 //Console.WriteLine($"i = {i}");
                return i;
            });
            Console.WriteLine("方法{0} 执行完毕,任务的ID->{1},托管线程->{2}", i, r.Id, Thread.CurrentThread.ManagedThreadId);

            if (i == 49)
                Console.WriteLine("用时{0}", (DateTime.Now - time).TotalMilliseconds);
            return r;
        }   
    }

输出结果就不完全贴出来了,任意截取三个作为分析。取出方法23、方法24和方法29执行的结果,执行顺序保持不变。

方法23 执行完毕,任务的ID->24,托管线程->1
方法23 任务Id = 24 被执行,托管线程->1
方法23 调用完成 此任务的ID->24, 托管线程->1
方法24 执行完毕,任务的ID->25,托管线程->1
方法24 任务Id = 25 被执行,托管线程->1
方法24 调用完成 此任务的ID->25, 托管线程->1
方法29 执行完毕,任务的ID->30,托管线程->1
方法29 任务Id = 30 被执行,托管线程->1
方法29 调用完成 此任务的ID->30, 托管线程->1
方法23 任务ID->24 Task被执行,托管线程->10
方法24 任务ID->25 Task被执行,托管线程->4
方法29 任务ID->30 Task被执行,托管线程->7

没有被Async/await修饰的方法测试代码,在内存中的执行情况。

根据以上输出的结果可以看出,没有被Async/await修饰的方法,是在主线程中执行的。

如果在主线程中想得到Task.Run(()={ })中return i 的结果,需要调用Index.Result属性获取此任务的结果。如:

for (int i = 0; i < 50; i++)
 {
     Task index = method(asy, data, i);
     int result = index.Result;
     Console.WriteLine("方法{0} 调用完成 此任务的ID->{1}, 托管线程->{2}", i, index.Id, Thread.CurrentThread.ManagedThreadId);
 }

但是调用Index.Result又会阻塞主线程的执行,直到托管线程中执行完返回其结果,主线程才能继续执行,这样就达不到实现多线程的目的了。

如果不需要主线程获取结果,只需显示出来,那么可以在任务中加上输出显示结果即可:如:Console.WriteLine($"i = {i}");


最后来看一种Async修饰的方法,Task任务前被await修饰。
    class Program
    {
        static void Main(string[] args)
        {
            Asy_Class asy = new Asy_Class();
            asy.ReadResult += (o, e) => { Console.WriteLine($"返回的结果:{o.ToString()}"); };//执行回调,处理事件。
            DateTime dataAsync = DateTime.Now;
            for (int i = 0; i < 50; i++)
            {
                Task index = asy.MethodB(dataAsync, i);
                Console.WriteLine("异步方法{0} 调用完成 此任务的ID->{1}, 托管线程->{2}", i, index.Id, Thread.CurrentThread.ManagedThreadId);
            }
            Console.ReadKey();
        }
    }
    //测试类
    class Asy_Class
    {
        //声明一个事件,当返回结果时触发该事件。
        public event EventHandler ReadResult;

        /// 
        /// 异步方法
        /// 
        /// 
        /// 使用async/await:
        /// 在方法中,先执行完await标记的Task线程任务,等待任务完成后,再执行后面的语句。
        /// 如:
        ///     异步方法23  任务ID->47 Task被执行,托管线程->7
        ///     异步方法23 执行完毕,托管线程->7 结果:23
        /// "执行完毕"这个语句总是在"任务ID-> "语句的后面。
        /// 可以说明await是等待任务返回的结果。
        /// 
        /// 
        /// 
        /// 
        public async Task MethodB(DateTime time, int i)
        {
            int r = await Task.Run(() =>
            {
                Console.WriteLine("异步方法{0}  任务ID->{2} Task被执行,托管线程{1}", i, Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
                Thread.Sleep(100);
                return i;
            });
            ReadResult?.Invoke(r, new EventArgs());//触发事件
            Console.WriteLine("异步方法{0} 执行完毕,结果:{1}", i, r);

            if (i == 49)
                Console.WriteLine("用时{0}", (DateTime.Now - time).TotalMilliseconds);
            return r;
        }
    }

 输出结果同样不全贴出来了,任意截取两个作为分析。还是取出方法23、方法24执行的结果,执行顺序保持不变。

异步方法23 调用完成 此任务的ID->48, 托管线程->1
异步方法24 调用完成 此任务的ID->50, 托管线程->1
异步方法23  任务ID->47 Task被执行,托管线程->7
异步方法24  任务ID->49 Task被执行,托管线程->4
返回的结果:23
异步方法23 执行完毕,托管线程->7 结果:23
返回的结果:24
异步方法24 执行完毕,托管线程->4 结果:24
用时774.9525

同样被Async修饰的方法,Task任务前被await修饰测试代码,在内存中的执行情况。

以上仅是个人理解,如有不对的地方请指正,欢迎大家留言,转载请注明出处。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/910262.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号