C#中async/await的线程ID变化情况

2023-11-04

    

一、简单的起步

    Console.WriteLine($"主线程开始ID:{Thread.CurrentThread.ManagedThreadId}");//a
    await Task.Delay(100);//c
    Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//b


    结果:
        主线程开始ID:1
        主线程结束ID:4        
    
    1、问:async/await会创建新线程吗?
        答:async和await并不会直接创建新的线程,而是通过利用异步机制来实现非阻塞的异步操作。
        
            C#中的async和await关键字并不会创建新的线程。它们实际上是用于异步编程的语法糖。

            当使用async关键字修饰一个方法时,该方法可以被视为一个异步方法。在异步方法内部,可以使用await关键字来等待其他异步操作的完成。

            当遇到await关键字时,异步方法会暂时挂起,让出当前线程的控制权,而不会阻塞线程。当被await的异步操作完成后,异步方法会恢复执行,并返回结果。

            在大多数情况下,异步操作并不会创建新的线程,而是通过利用I/O完成端口或其他异步机制来实现异步操作。这样可以避免创建额外的线程,提高程序的性能和资源利用率。

            注意,如果使用了Task.Run等方法来包装一个同步的阻塞操作,那么它可能会在新的线程上执行。这样做的目的是为了将阻塞操作转换为异步操作,以避免阻塞主线程。


    2、问:异步方法会暂时挂起,是什么意思?
        答:在遇到await Task.Delay(100)时,异步方法会暂时挂起,并让出当前线程的控制权。这里的挂起并不是指线程被挂起或阻塞,而是指异步方法暂时停止执行,并将控制权返回给调用它的线程(主线程)。
        
            当遇到await Task.Delay(100)时,它会创建一个延时任务,该任务会在指定的时间(这里是100毫秒)后完成。然后,异步方法会注册一个回调函数,告诉任务完成后要继续执行下一步。
            在挂起期间,异步方法不会占用线程资源,而是让线程可以执行其他任务。这样可以提高程序的并发性和资源利用率。
            
            一旦延时任务完成,异步方法会被唤醒,并继续执行后续的代码。这时,并不是创建新的线程来执行延时操作,而是通过异步机制来实现非阻塞的延时操作。
            
            具体来说,当异步方法遇到await Task.Delay(100)时,它会将延时任务交给.NET运行时的任务调度器(Task Scheduler)管理。任务调度器会将延时任务放入等待队列中,并继续执行其他任务。
            
            在指定的时间(100毫秒)后,任务调度器会将延时任务标记为完成,并将其添加到就绪队列中。当调度器调度到该任务时,它会通知异步方法继续执行,并返回到原来的线程(主线程)上。
            
            总之,异步方法的挂起并不是线程的挂起或阻塞,而是暂时停止执行,并让出当前线程的控制权。在挂起期间,线程可以执行其他任务。异步方法通过异步机制来实现非阻塞的延时操作,让出当前线程的控制权,并在延时任务完成后继续执行。
        
        
    3、问:遇到await时主线程在做啥,玩泥巴吗?
        答:是的,它不是阻塞,而是去干其它事去了。
        
            当遇到await关键字时,主线程会暂时挂起(挂起点),这并不会阻塞主线程的执行,而是让出当前线程的控制权,允许主线程去执行其他任务。
            
            同时,await关键字会将异步操作交给任务调度器来管理。任务调度器会根据当前的线程池状态和调度策略,将异步操作分配给适当的线程执行。
            
            当异步操作完成后,任务调度器会通知异步方法继续执行。这时,可能会发生线程切换,执行剩下的代码的线程可能是之前执行异步操作的线程,也可能是其他线程。
            
            这种机制使得异步方法能够以非阻塞的方式执行,并允许主线程在等待异步操作完成时继续执行其他任务,提高了程序的并发性和响应性。
            
            注意,异步方法的挂起和恢复是由任务调度器来管理和控制的,具体的线程调度和切换机制是由.NET运行时来处理的。开发人员并不需要显式地关注线程的创建和管理,而是通过使用async和await来编写简洁、清晰的异步代码。

    
    4、问:await也要开线程吧?
        答:不一定,大多数情况下,异步操作并不会创建新的线程,而是利用异步机制(如I/O完成端口)来实现非阻塞的异步操作。
        
            通过使用异步机制,可以将阻塞的I/O操作转换为异步的操作,而不需要创建新的线程。这样可以避免线程的创建和销毁,提高程序的性能和资源利用率。
            
            除了线程池中的线程,调度器也可以使用其他的执行上下文,比如使用事件触发器或计时器来执行异步操作。这种情况下,调度器会将异步操作添加到事件队列或计时器队列中,并在适当的时候触发事件或计时器来执行异步操作。
            
            然而,有些情况下,异步操作可能会创建新的线程。例如:

            (1)使用Task.Run等方法:
            如果使用Task.Run等方法来包装一个同步的阻塞操作,那么它可能会在新的线程上执行。这样做的目的是为了将阻塞操作转换为异步操作,以避免阻塞主线程。

            (2)自定义线程池:
            在某些情况下,开发人员可以自定义线程池来控制异步操作的执行。这可能涉及到线程的创建和管理,以满足特定的需求。

            注意,创建新的线程可能会增系统资源的开销,并且需要进行线程同步和管理。因此,在设计和实现异步操作时,该根据实际情况和需求来选择合适的方式,以平衡性能、资源利用率和代码复杂性。
            
            总结,大多数情况下,异步操作不会创建新的线程,而是利用异步机制来实现非阻塞的操作。但在某些情况下,可能会涉及到创建新的线程来执行异步操作,以满足特定的需求。
    
    
    5、问: await完成后,主线程的ID可能不是1了?
        答:是的。
        
            当遇到await关键字时,主线程会暂时挂起(挂起点),这并不会阻塞主线程的执行,而是让出当前线程的控制权,允许主线程去执行其他任务。
            
            同时,await关键字会将异步操作交给任务调度器来管理。任务调度器会根据当前的线程池状态和调度策略,将异步操作分配给适当的线程执行。
            
            当异步操作完成后,任务调度器会通知异步方法继续执行。这时,可能会发生线程切换,执行剩下的代码的线程可能是之前执行异步操作的线程,也可能是其他线程。
            
            这种机制使得异步方法能够以非阻塞的方式执行,并允许主线程在等待异步操作完成时继续执行其他任务,提高了程序的并发性和响应性。
            
            具体的来说就是:
            当异步操作完成后,任务调度器会通知异步方法继续执行,具体是通过将执行权从之前的线程切换回到原来挂起点的代码,然后继续执行下面的代码。
            
            在异步方法中,遇到await关键字时,会将await之后的代码封装为一个延续(continuation),并注册到异步操作的完成事件上。
            
            当异步操作完成后,任务调度器会将延续添加到就绪队列中,等待调度执行。一旦调度器调度到该延续,它会通知异步方法继续执行,切换回原来的挂起点。
            这个通知是通过线程切换和调度机制实现的。具体来说,任务调度器会选择一个可用的线程(可能是之前执行异步操作的线程,也可能是其他线程),并将执行权转移给该线程。这样,异步方法就可以继续执行await之后的代码。
            
            注意,异步方法的继续执行并不是立即发生的,而是在调度器选择并分配线程之后才会发生。具体的线程调度和切换机制是由.NET运行时和任务调度器来处理的,开发人员不需要显式地管理和控制。
            
            总之,异步操作完成后,任务调度器会通过线程切换和调度机制将执行权切换回原来的挂起点,通知异步方法继续执行下面的代码。这样可以实现非阻塞的异步操作和代码的顺序执行。
    

    6、问:那上面的的await应该有答案了吗?
        答:是的,上面可以知道在c处挂起,主线程玩泥巴,这个延时交给任务调度器(不一定是创建线程,也可能是事件回调机制),延时完成后回来,任务调度器会选择一个可用线程(可能是主线程,可能是前面异步操作线程,也有可能是新的其它线程,谁闲谁知道呢,服从领导就OK啦),继续执行c处后面的代码。所以d的ID是随机的,谁也说不准。
    
    
    7、问:什么是上下文?
        答:人话就是,上下文(Context)是指执行代码时所处的环境和状态。它包含了一些与执行相关的信息,如线程调度器、同步上下文、同步上下文流动等。
            比如,你工作的场所,场景,环境。有电脑,笔,桌子,办公室,等等。
    
    
    8、问:所有线程都有上下文?
        答:在异步编程中,线程执行时都会有上下文。上下文提供了执行环境和状态,包括线程的调度、同步上下文、同步上下文流动等。
            人话就是:所有鱼都有自己的生存环境。
    
    
    9、问:上下文的切换都会有消耗资源?
        答:对的。
            切换线程上下文可能会涉及一些开销,包括线程的切换、上下文的保存和恢复等。这是因为不同的线程可能具有不同的执行环境和状态,需要进行一些额外的操作来确保正确的执行。
            人话就是:如果你原来在A处办公,现在调整到B处去办公,你当然需要搬运、布置,打扫等工作,肯定有些许时间的消耗。
        
  


二、再添加一个异步

    Console.WriteLine($"主线程开始ID:{Environment.CurrentManagedThreadId}");//a
    Task task = Task.Run(() =>
    {
        Console.WriteLine($"异步线程ID开始:{Environment.CurrentManagedThreadId}");//b
        Thread.Sleep(10);//c
        Console.WriteLine($"异步线程ID结束:{Environment.CurrentManagedThreadId}");//d
    });
    await Task.Delay(100);//e
    Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//f
    Console.ReadKey();    


    结果:
        主线程开始ID:1
        异步线程ID开始:3
        异步线程ID结束:3
        主线程结束ID:4    
    a处为主线程ID为1,然后新开一个异步线程task,一闪而过去执行e处,又是一个异步线程但有await。所以f是随机的,可能是1,可能是4,但不可能是3,因为此时3被c处占用。
    对于b和d,因为c是同步线程,所以b和d都是在同一个线程中执行,它们的ID是相同的为3.
    
    
    修改一:将C的10毫秒改变为1000毫秒。

    Console.WriteLine($"主线程开始ID:{Environment.CurrentManagedThreadId}");//a
    Task task = Task.Run(() =>
    {
        Console.WriteLine($"异步线程ID开始:{Environment.CurrentManagedThreadId}");//b
        Thread.Sleep(1000);//c
        Console.WriteLine($"异步线程ID结束:{Environment.CurrentManagedThreadId}");//d
    });
    await Task.Delay(100);//e
    Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//f
    Console.ReadKey();    


    结果:
        主线程开始ID:1
        异步线程ID开始:3
        主线程结束ID:4
        异步线程ID结束:3    
    同样b和d仍然同一线程内,肯定相同,所以两者为3。
    e处之后,f的ID是随机的,但它不可能是3,此是3仍然在c处占用.
    
    
    修改二:把e处改为thread.Sleep(1000)

    Console.WriteLine($"主线程开始ID:{Environment.CurrentManagedThreadId}");//a
    Task task = Task.Run(() =>
    {
        Console.WriteLine($"异步线程ID开始:{Environment.CurrentManagedThreadId}");//b
        Thread.Sleep(10);//c
        Console.WriteLine($"异步线程ID结束:{Environment.CurrentManagedThreadId}");//d
    });
    Thread.Sleep(1000);//e
    Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//f


    结果:
        主线程开始ID:1
        异步线程ID开始:3
        异步线程ID结束:3
        主线程结束ID:1    
    bcd处一样为3.
    e处为同步。由主线程执行ID为1,所以后面的f处也为1.
    


三、新加异步中的Await


    1、两个线程,第一个异步中异步,第二同步:

        Console.WriteLine($"主线程开始ID:{Environment.CurrentManagedThreadId}");//a
        Task task = Task.Run(async () =>
        {
            Console.WriteLine($"异步线程ID开始:{Environment.CurrentManagedThreadId}");//b
            await Task.Delay(10);//c
            Console.WriteLine($"异步线程ID结束:{Environment.CurrentManagedThreadId}");//d
        });
        Thread.Sleep(1000);//e
        Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//f    


        结果:
            主线程开始ID:1
            异步线程ID开始:3
            异步线程ID结束:4
            主线程结束ID:1
        e处为同步线程,在主线程中,所以到了f时是主线程。
        b处由Task.Run申请的线程,ID为3,经过c后,原ID为3的挂起,在延时做完后,恢复执行后面代码时,由调度器选择线程来执行后面的d处,所以d的ID是随机的,但不可能是1.
        
        
    2、两个线程,第一个异步中异步,第二次await异步。

        Console.WriteLine($"主线程开始ID:{Environment.CurrentManagedThreadId}");//a
        Task task = Task.Run(async () =>
        {
            Console.WriteLine($"异步线程ID开始:{Environment.CurrentManagedThreadId}");//b
            await Task.Delay(10);//c
            Console.WriteLine($"异步线程ID结束:{Environment.CurrentManagedThreadId}");//d
        });
        await Task.Delay(1000);//e
        Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//f


        结果: 
            主线程开始ID:1
            异步线程ID开始:3
            异步线程ID结束:5
            主线程结束ID:3    
        b处为异步线程ID为3,经过C处后,d处随机,显示为5.
        e处返回时,b,d使用的线程(3和5)已经返回给线程池,也即线程池是有可能再次给e后面分配1,3,5等,所以这里显示是3.
        
    
    3、修改上面,把延时调整一下,c处占久点

        Console.WriteLine($"主线程开始ID:{Environment.CurrentManagedThreadId}");//a
        Task task = Task.Run(async () =>
        {
            Console.WriteLine($"异步线程ID开始:{Environment.CurrentManagedThreadId}");//b
            await Task.Delay(1000);//c
            Console.WriteLine($"异步线程ID结束:{Environment.CurrentManagedThreadId}");//d
        });
        await Task.Delay(10);//e
        Console.WriteLine($"主线程结束ID:{Environment.CurrentManagedThreadId}");//f


        结果:
            主线程开始ID:1
            异步线程ID开始:3
            主线程结束ID:5
            异步线程ID结束:6
        b处分配ID为3,然后c处等待,在d处随机得到分配的ID,由于它是1000毫秒后,即所有任务都执行完成了,只有它,所以它的随机分配是任何可能,可以是3,5,6等。
        e处过后,返回时由于b处占用了3(哪怕是挂起),所以在f处调度器随机分配线程不可能ID为3,所以上面分配的是5.


    
四、Configureawait的失效

    Console.WriteLine($"主线程开始ID:[{Environment.CurrentManagedThreadId}]");//a
    await Task.Run(async () =>
    {
        Console.WriteLine($"异步线程开始ID:[{Environment.CurrentManagedThreadId}]");//b
        await Task.Delay(1000);//c
        Console.WriteLine($"异步线程结束ID:[{Environment.CurrentManagedThreadId}]");//d
    }).ConfigureAwait(true);//e
    Console.WriteLine($"主线程结束ID:[{Environment.CurrentManagedThreadId}]");//f


    结果:
        主线程开始ID:[1]
        异步线程开始ID:[3]
        异步线程结束ID:[4]
        主线程结束ID:[4]
    上面b处ID结果为3,然后经c处后,在d处随机分配。结果是4.
    f处由于前面await的原因,同样也是随机分配,它是最后执行,所以ID有任意的可能(看调度器的分配了)。
    
    这里,说明的是无论Configiure为True还是False,最后都要以await结束,都要返回到主线程的上下文中,所以它“失效了”.
    

    Task.Run会将异步操作放入线程池中执行,而await会在异步操作完成之前阻塞主线程。当异步操作完成后,会尝试切换回主调线程执行await之后的代码。无论e处的参数是true还是false,异步操作完成后,恢复时都会尝试切换回主调线程执行d处或者f处的代码。
    
    
    
    问:为什么d与f处的线程大多数是一样的?
    答:这个不是绝对的。按优化概率可能是这样。
        调度器通常会尽量将执行权切换回刚完成的异步线程,以继续执行原先挂起的代码。这种方式可以减少线程切换和上下文切换的成本,提高执行效率。
        
        当一个异步任务完成后,调度器会考虑以下几个因素来决定是否将执行权切换回刚完成的异步线程:

        (1)异步线程的可用性:
        如果刚完成的异步线程仍然可用,调度器会优先选择它来执行后续代码,因为这样可以避免线程切换的开销。

        (2)异步线程的负载:
        如果刚完成的异步线程当前正在执行其他任务,调度器可能会选择一个空闲的线程来执行后续代码,以平衡负载。

        (3)上下文切换的成本:
        如果切换到刚完成的异步线程的上下文比切换到其他线程的上下文更低廉,调度器可能会优先选择它来执行后续代码。

        注意,具体的调度策略和行为取决于调度器的实现和配置。不同的调度器可能有不同的优化策略和行为。因此,在实际应用中,可能会出现一些例外情况,导致执行权并不会立即切换回刚完成的异步线程。
    
    
    
    改变一下它的优化,再增加一句await:

    Console.WriteLine($"主线程开始ID:[{Environment.CurrentManagedThreadId}]");//a
    await Task.Run(async () =>
    {
        Console.WriteLine($"异步线程开始ID:[{Environment.CurrentManagedThreadId}]");//b
        await Task.Delay(1000);//c
        Console.WriteLine($"异步线程结束ID:[{Environment.CurrentManagedThreadId}]");//d
    }).ConfigureAwait(true);//e
    await Task.Delay(1000);
    Console.WriteLine($"主线程结束ID:[{Environment.CurrentManagedThreadId}]");//f    


    结果:
        主线程开始ID:[1]
        异步线程开始ID:[3]
        异步线程结束ID:[4]
        主线程结束ID:[3]    
    f处变成了3,看来调试器总是在当前比较优闲的ID(大概猜测就是1,3,4中选秀)。
    
    而且,最难得的是大约有10次运行,终于找到一个难得的截图:
  


    再次证明,优化是有规则的,所以中奖概率高,但并非绝对的。
    

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#中async/await的线程ID变化情况 的相关文章

  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • 动态加载程序集的应用程序配置

    我正在尝试将模块动态加载到我的应用程序中 但我想为每个模块指定单独的 app config 文件 假设我的主应用程序有以下 app config 设置
  • 秒表有最长运行时间吗?

    多久可以Stopwatch在 NET 中运行 如果达到该限制 它会回绕到负数还是从 0 重新开始 Stopwatch Elapsed返回一个TimeSpan From MSDN https learn microsoft com en us
  • 在哪里可以找到列出 SSE 内在函数操作的官方参考资料?

    是否有官方参考列出了 GCC 的 SSE 内部函数的操作 即 头文件中的函数 除了 Intel 的 vol 2 PDF 手册外 还有一个在线内在指南 https www intel com content www us en docs in
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 如何在 C 中调用采用匿名结构的函数?

    如何在 C 中调用采用匿名结构的函数 比如这个函数 void func struct int x p printf i n p x 当提供原型的函数声明在范围内时 调用该函数的参数必须具有与原型中声明的类型兼容的类型 其中 兼容 具有标准定
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 如何查看网络连接状态是否发生变化?

    我正在编写一个应用程序 用于检查计算机是否连接到某个特定网络 并为我们的用户带来一些魔力 该应用程序将在后台运行并执行检查是否用户请求 托盘中的菜单 我还希望应用程序能够自动检查用户是否从有线更改为无线 或者断开连接并连接到新网络 并执行魔
  • 这些作业之间是否存在顺序点?

    以下代码中的两个赋值之间是否存在序列点 f f x 1 1 x 2 不 没有 在这种情况下 标准确实是含糊不清的 如果你想确认这一点 gcc 有这个非常酷的选项 Wsequence point在这种情况下 它会警告您该操作可能未定义
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • cmake 将标头包含到每个源文件中

    其实我有一个简单的问题 但找不到答案 也许你可以给我指一个副本 所以 问题是 是否可以告诉 cmake 指示编译器在每个源文件的开头自动包含一些头文件 这样就不需要放置 include foo h 了 谢谢 CMake 没有针对此特定用例的
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 如何在文本框中插入图像

    有没有办法在文本框中插入图像 我正在开发一个聊天应用程序 我想用图标图像更改值 等 但我找不到如何在文本框中插入图像 Thanks 如果您使用 RichTextBox 进行聊天 请查看Paste http msdn microsoft co
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写

随机推荐

  • H5实现高德地图的uni.chooseLocation

    在uniapp中获取当前定位和选择当前位置都是做了兼容 在各个平台都可以使用 这篇文章讲解如何定位当前位置 搜索位置 点击进行定位在H5中实现uni chooseLocation 如下图所示 左侧微信小程序的选择位置 右侧为高德地图在H5中
  • 微信小程序 修改三方组件中的自带样式

    众所周知 微信小程序会引用诸如vant weiui等三方组件 当我们想要对组件自带样式进行修改的时候该怎么做呢 1 在调试器中找到想要修改样式的组件的class类名 在对应的wxss中添加样式 此时样式未生效 2 官方文档中提到可以在ext
  • python写的串口助手并连接腾讯云服务器数据库

    结合上一期的基于pyqt5开发的图书管理系统UI 带登录页面 文章做一个此章节的补充 因为老师说需要结合数据库实现登录系统 于是我就想起了自己在腾讯云上买的一个服务器 因此经过百度查询大量的资料 功夫不负有心人 在这个Pyqt5实现的可视化
  • three.js展示obj模型

    利用three js展示obj模型 环境 必须是在web服务器中 下载objShow js
  • 【UART】Verilog实现UART接收和发送模块

    目录 写在前面 UART 工作原理 UART 接收部分 UART RX 模块图 UART RX 时序图 Verilog 实现 UART RX 模块 UART 发送部分 UART TX 模块图 UART TX 时序图 Verilog 实现 U
  • linux如何同时执行两个命令,如何同时运行两个或者多个终端命令

    选项一 分号 运算符 分号 运算符允许你连续执行多个命令 而不管前面的每个命令是否成功 例如 打开终端窗口 在Ubuntu和Linux Mint中 Ctrl Alt T 然后 在一行中键入以下三个命令 用分号分隔 然后按Enter 这会列出
  • 09、用户、组(一):基本分类

    1 用户类别 管理员 普通用户 系统用户 登录用户 2 用户标识 UserID UID 16bits二进制数字 0 65535 管理员 0 普通用户 1 65635 系统用户 1 499 CentOS6 1 999 CentOS7 登录用户
  • springboot2.0 redis使用lettuce连接包实现分布式锁关键词setnx

    springboot升级到2 0之后 关联的spring data redis默认使用的连接包也从原本的jedis改为了性能更好 且线程安全的使用netty实现的lettuce连接包 鉴于spring data默认只提供了setnx不带过期
  • Unable to negotiate with XXXX port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss

    问题描述 代码仓库已经添加了ssh公钥之后 克隆代码到本地时就报了这个问题 执行命令 git clone git xxxxxxxxxxxxx git 不能正常clone代码 报错信息如下 Unable to negotiate with x
  • JVM 面试深入理解内存模型和垃圾回收(二)

    JVM 面试深入理解内存模型和垃圾回收 二 文章目录 JVM 面试深入理解内存模型和垃圾回收 二 1 运行时数据区域 1 1 The PC Register 1 2 Java Virtual Machine Stacks 1 2 1 Fra
  • MongoDB Replica Sets + Sharding 实战

    一 Replica Sets 复制集 MongoDB 支持在多个机器中通过异步复制达到故障转移和实现冗余 多机器中同一时刻只有一台是用于写操作 正是由于这个情况 为 MongoDB 提供了数据一致性的保障 担当Primary 角色的机器能把
  • image点击事件

    self headImage userInteractionEnabled YES UITapGestureRecognizer singleTap1 UITapGestureRecognizer alloc initWithTarget
  • 实验八 模板

    一 实验目的和要求 1 能够使用C 模板机制定义重载函数 2 能够实例化及使用模板函数 3 能够实例化和使用模板类 4 应用标准C 模板库 STL 通用算法和函数对象实现查找和排序 二 实验内容 1 分析并调试下列程序 了解函数模板的使用
  • GJM : 【技术干货】给The Lab Renderer for Unity中地形添加阴影

    感谢您的阅读 喜欢的 有用的就请大哥大嫂们高抬贵手 推荐一下 吧 你的精神支持是博主强大的写作动力以及转载收藏动力 欢迎转载 版权声明 本文原创发表于 请点击连接前往 未经作者同意必须保留此段声明 如有问题请联系我 侵立删 谢谢 我的博客
  • R4 STM32高级定时器笔记之PWM互补输出

    STM32高级定时器笔记之PWM互补输出 程序功能 通过两个GPIO 输出相反的PWM信号 带死区时间和刹车控制 PWM为50 要配置几个寄存器 CNT计数器 CCR输出比较寄存器器 输入捕获寄存器 ARR自动重装载寄存器 最大65535
  • 数据结构之Trie树

    目录 前言 什么是Trie树 如何实现一棵Trie树 Trie 树与散列表 红黑树的比较 问题 总结 参考资料 前言 搜索引擎的搜索关键词提示功能 我想你应该不陌生吧 为了方便快速输入 当你在搜索引擎的搜索框中 输入要搜索的文字的某一部分的
  • C语言共用体-union的用法

    定义格式 union 共用体名 成员列表 共用体特点 1 占用的内存等于最长的成员占用的内存 2 共用体使用了内存覆盖技术 同一时刻只能保存一个成员的值 如果对新的成员赋值 就会把原来成员的值覆盖掉 会影响其余所有成员 实例说明 typed
  • 二进制流:C++中使用 (char *)& 传递int型值

    文章目录 前言 一 利用二进制流传递整形数组值 1 1 整形数组 1 2 二进制流 1 2 1 那如何将整形数值依次按一个字节存放入二进制流中呢 1 2 2 如何重构回整形数值 二 结构体数组赋值 前言 此前参与罗技G29方向盘远程遥控au
  • 关于运行gitblit.cmd中的@java -cp gitblit.jar;“%CD%\ext\*“ com.gitblit.GitBlitServer --baseFold data报错

    关于运行gitblit cmd中的 java cp gitblit jar CD ext com gitblit GitBlitServer baseFold data报错 问题 关于运行gitblit cmd中的 java cp gitb
  • C#中async/await的线程ID变化情况

    一 简单的起步 Console WriteLine 主线程开始ID Thread CurrentThread ManagedThreadId a await Task Delay 100 c Console WriteLine 主线程结束I