之前一直有个疑问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
根据调试现象来看,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修饰的方法,是在主线程中执行的。
如果在主线程中想得到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修饰测试代码,在内存中的执行情况。
以上仅是个人理解,如有不对的地方请指正,欢迎大家留言,转载请注明出处。



