当前位置: 首页 > 编程日记 > 正文

C#多线程学习

任何程序在执行时,至少有一个主线程。在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。

Thread类有几个至关重要的方法,描述如下:

Start():启动线程;

Sleep(int):静态方法,暂停当前线程指定的毫秒数;

Abort():通常使用该方法来终止一个线程;

Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;

Resume():恢复被Suspend()方法挂起的线程的执行。

 

一个直观印象的线程示例:

using System;
using System.Threading;

namespace ThreadTest
{
  
class RunIt
  {
    [STAThread]
    
static void Main(string[] args)
    {
      Thread.CurrentThread.Name
="System Thread";//给当前线程起名为"System Thread"
Console.WriteLine(Thread.CurrentThread.Name+"'Status:"+Thread.CurrentThread.ThreadState);
      Console.ReadLine();
    }
  }
}

输出如下:

System Thread's Status:Running

 

在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。

1.所有线程都是依附于Main()函数所在的线程的,Main()函数是C#程序的入口,起始线程可以称之为主线程。如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。所有的线程虽然在微观上是串行执行的,但是在宏观上你完全可以认为它们在并行执行。

 

2.ThreadState 属性的取值如下,这个属性代表了线程运行时状态,在不同的情况下有不同的值,我们有时候可以通过对该值的判断来设计程序流程。:

Aborted:线程已停止;

AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止;

Background:线程在后台执行,与属性Thread.IsBackground有关;

Running:线程正在正常运行;

Stopped:线程已经被停止;

StopRequested:线程正在被要求停止;

Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行);

SuspendRequested:线程正在要求被挂起,但是未来得及响应;

Unstarted:未调用Thread.Start()开始线程的运行;

WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态;

 

3.当线程之间争夺CPU时间时,CPU 是按照线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。

例:

using System;
using System.Threading;

namespace ThreadTest
{
public class Alpha
{
public void Beta()
{
while (true)
{
Console.WriteLine(
"Alpha.Beta is running in its own thread.");
}
}
};

public class Simple
{
public static int Main()
{
  Console.WriteLine(
"Thread Start/Stop/Join Sample");
Alpha oAlpha
= new Alpha();
   file:
//这里创建一个线程,使之执行Alpha类的Beta()方法
   Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
   oThread.Start();
  
while (!oThread.IsAlive)
   Thread.Sleep(
1);
   oThread.Abort();
   oThread.Join();
   Console.WriteLine();
   Console.WriteLine(
"Alpha.Beta has finished");
  
try
   {
     Console.WriteLine(
"Try to restart the Alpha.Beta thread");
     oThread.Start();
   }
  
catch (ThreadStateException)
   {
     Console.Write(
"ThreadStateException trying to restart Alpha.Beta. ");
     Console.WriteLine(
"Expected since aborted threads cannot be restarted.");
     Console.ReadLine();
   }
  
return 0;
   }
}
}

 

C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:

lock(expression) statement_block

expression代表你希望跟踪的对象,通常是对象引用。

  • 如果你想保护一个类的实例,一般地,你可以使用this;
  • 如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

示例如下:

using System;
using System.Threading;

namespace ThreadSimple
{
   
internal class Account
    {
       
int balance;
        Random r
= new Random();
       
       
internal Account(int initial)
        {
            balance
= initial;
        }
       
internal int Withdraw(int amount)
        {
           
if (balance < 0)
            {
               
//如果balance小于0则抛出异常
                throw new Exception("Negative Balance");
            }
           
//下面的代码保证在当前线程修改balance的值完成之前
           
//不会有其他线程也执行这段代码来修改balance的值
           
//因此,balance的值是不可能小于0 的
            lock (this)
            {
                Console.WriteLine(
"Current Thread:"+Thread.CurrentThread.Name);
               
//如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
               
//另外一个线程却执行了balance=balance-amount修改了balance的值
               
//而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
               
//但是,这个线程却继续执行balance=balance-amount,所以导致balance可能小于0
                if (balance >= amount)
                {
                    Thread.Sleep(
5);
                    balance
= balance - amount;
                   
return amount;
                }
               
else
                {
                   
return 0; // transaction rejected
                  }
            }
        }
       
internal void DoTransactions()
        {
           
for (int i = 0; i < 100; i++)
            Withdraw(r.Next(
-50, 100));
        }
    }
   
internal class Test
    {
       
static internal Thread[] threads = new Thread[10];
       
public static void Main()
        {
            Account acc
= new Account (0);
           
for (int i = 0; i < 10; i++)
            {
                Thread t
= new Thread(new ThreadStart(acc.DoTransactions));
                threads[i]
= t;
            }
           
for (int i = 0; i < 10; i++)
                threads[i].Name
=i.ToString();
           
for (int i = 0; i < 10; i++)
                threads[i].Start();
            Console.ReadLine();
        }
    }
}

 

当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。

Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。 Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形:

......

Queue oQueue=new Queue();

......

Monitor.Enter(oQueue);

......//现在oQueue对象只能被当前线程操纵了

Monitor.Exit(oQueue);//释放锁

 

在多线程的程序中,经常会出现两种情况:

一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应

这一般使用ThreadPool(线程池)来解决;

另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒

这一般使用Timer(定时器)来解决;

ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),

将线程安放在线程池里,需使用ThreadPool.QueueUserWorkItem()方法,该方法的原型如下:

//将一个线程放进线程池,该线程的Start()方法将调用WaitCallback代理对象代表的函数

public static bool QueueUserWorkItem(WaitCallback);

//重载的方法如下,参数object将传递给WaitCallback所代表的方法

public static bool QueueUserWorkItem(WaitCallback, object);

在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠WaitCallback代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。

首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。

在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:

Reset()方法:将其设置为无信号状态;

Set()方法:将其设置为有信号状态。

WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活.然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。

using System;
using System.Collections;
using System.Threading;

namespace ThreadExample
{
   
//这是用来保存信息的数据结构,将作为参数被传递
    public class SomeState
    {
     
public int Cookie;
     
public SomeState(int iCookie)
      {
        Cookie
= iCookie;
      }
    }

   
public class Alpha
    {
  
public Hashtable HashCount;
  
public ManualResetEvent eventX;
  
public static int iCount = 0;
  
public static int iMaxCount = 0;
  
       
public Alpha(int MaxCount)
  {
         HashCount
= new Hashtable(MaxCount);
         iMaxCount
= MaxCount;
  }

  
//线程池里的线程将调用Beta()方法
  public void Beta(Object state)
  {
     
//输出当前线程的hash编码值和Cookie的值
         Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),((SomeState)state).Cookie);
      Console.WriteLine(
"HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode());
     
lock (HashCount)
      {
       
//如果当前的Hash表中没有当前线程的Hash值,则添加之
        if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
             HashCount.Add (Thread.CurrentThread.GetHashCode(),
0);
         HashCount[Thread.CurrentThread.GetHashCode()]
=
            ((
int)HashCount[Thread.CurrentThread.GetHashCode()])+1;
      }
         
int iX = 2000;
          Thread.Sleep(iX);
         
//Interlocked.Increment()操作是一个原子操作,具体请看下面说明
          Interlocked.Increment(ref iCount);

         
if (iCount == iMaxCount)
          {
          Console.WriteLine();
        Console.WriteLine(
"Setting eventX ");
        eventX.Set();
        }
    }
  }

       
public class SimplePool
        {
           
public static int Main(string[] args)
            {
                Console.WriteLine(
"Thread Pool Sample:");
               
bool W2K = false;
               
int MaxCount = 10;//允许线程池中运行最多10个线程
               
//新建ManualResetEvent对象并且初始化为无信号状态
                ManualResetEvent eventX = new ManualResetEvent(false);
                Console.WriteLine(
"Queuing {0} items to Thread Pool", MaxCount);
                Alpha oAlpha
= new Alpha(MaxCount);
               
//创建工作项
               
//注意初始化oAlpha对象的eventX属性
                oAlpha.eventX = eventX;
                Console.WriteLine(
"Queue to Thread Pool 0");
               
try
                {
                   
//将工作项装入线程池
                   
//这里要用到Windows 2000以上版本才有的API,所以可能出现NotSupportException异常
                    ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(0));
                    W2K
= true;
                }
               
catch (NotSupportedException)
                {
                    Console.WriteLine(
"These API's may fail when called on a non-Windows 2000 system.");
                    W2K
= false;
                }
               
if (W2K)//如果当前系统支持ThreadPool的方法.
                {
                   
for (int iItem=1;iItem < MaxCount;iItem++)
                    {
                       
//插入队列元素
                        Console.WriteLine("Queue to Thread Pool {0}", iItem);
                        ThreadPool.QueueUserWorkItem(
new WaitCallback(oAlpha.Beta), new SomeState(iItem));
                    }
                    Console.WriteLine(
"Waiting for Thread Pool to drain");
                   
//等待事件的完成,即线程调用ManualResetEvent.Set()方法
                    eventX.WaitOne(Timeout.Infinite,true);
                   
//WaitOne()方法使调用它的线程等待直到eventX.Set()方法被调用
                    Console.WriteLine("Thread Pool has been drained (Event fired)");
                    Console.WriteLine();
                    Console.WriteLine(
"Load across threads");
                   
foreach(object o in oAlpha.HashCount.Keys)
                        Console.WriteLine(
"{0} {1}", o, oAlpha.HashCount[o]);
                }
                Console.ReadLine();
               
return 0;
            }
        }
    }
}

 

Timer类:设置一个定时器,定时执行用户指定的函数。

定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数。

初始化一个Timer对象:

Timer timer = new Timer(timerDelegate, s,1000, 1000);

// 第一个参数:指定了TimerCallback 委托,表示要执行的方法;

// 第二个参数:一个包含回调方法要使用的信息的对象,或者为空引用;

// 第三个参数:延迟时间——计时开始的时刻距现在的时间,单位是毫秒,指定为“0”表示立即启动计时器;

// 第四个参数:定时器的时间间隔——计时开始以后,每隔这么长的一段时间,TimerCallback所代表的方法将被调用一次,单位也是毫秒。指定 Timeout.Infinite 可以禁用定期终止。

Timer.Change()方法:修改定时器的设置。(这是一个参数类型重载的方法)

使用示例: timer.Change(1000,2000);

如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类。线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只有等待。

其中还用到AutoResetEvent类的对象,可以把它理解为一个信号灯。这里用它的有信号状态来表示一个线程的结束。

// AutoResetEvent.Set()方法设置它为有信号状态

// AutoResetEvent.Reset()方法设置它为无信号状态

using System;
using System.Threading;

namespace ThreadExample
{
   
public class MutexSample
    {
     
static Mutex gM1;
     
static Mutex gM2;
     
const int ITERS = 100;
     
static AutoResetEvent Event1 = new AutoResetEvent(false);
     
static AutoResetEvent Event2 = new AutoResetEvent(false);
     
static AutoResetEvent Event3 = new AutoResetEvent(false);
     
static AutoResetEvent Event4 = new AutoResetEvent(false);

     
public static void Main(String[] args)
      {
            Console.WriteLine(
"Mutex Sample ");
           
//创建一个Mutex对象,并且命名为MyMutex
            gM1 = new Mutex(true,"MyMutex");
           
//创建一个未命名的Mutex 对象.
            gM2 = new Mutex(true);
            Console.WriteLine(
" - Main Owns gM1 and gM2");

            AutoResetEvent[] evs
= new AutoResetEvent[4];
            evs[
0] = Event1; //为后面的线程t1,t2,t3,t4定义AutoResetEvent对象
            evs[1] = Event2;
            evs[
2] = Event3;
            evs[
3] = Event4;

            MutexSample tm
= new MutexSample( );
            Thread t1
= new Thread(new ThreadStart(tm.t1Start));
            Thread t2
= new Thread(new ThreadStart(tm.t2Start));
            Thread t3
= new Thread(new ThreadStart(tm.t3Start));
            Thread t4
= new Thread(new ThreadStart(tm.t4Start));
            t1.Start( );
// 使用Mutex.WaitAll()方法等待一个Mutex数组中的对象全部被释放
            t2.Start( );// 使用Mutex.WaitOne()方法等待gM1的释放
            t3.Start( );// 使用Mutex.WaitAny()方法等待一个Mutex数组中任意一个对象被释放
            t4.Start( );// 使用Mutex.WaitOne()方法等待gM2的释放

            Thread.Sleep(
2000);
            Console.WriteLine(
" - Main releases gM1");
            gM1.ReleaseMutex( );
//线程t2,t3结束条件满足

            Thread.Sleep(
1000);
            Console.WriteLine(
" - Main releases gM2");
            gM2.ReleaseMutex( );
//线程t1,t4结束条件满足

           
//等待所有四个线程结束
            WaitHandle.WaitAll(evs);
            Console.WriteLine(
" Mutex Sample");
            Console.ReadLine();
      }

     
public void t1Start( )
      {
            Console.WriteLine(
"t1Start started, Mutex.WaitAll(Mutex[])");
            Mutex[] gMs
= new Mutex[2];
            gMs[
0] = gM1;//创建一个Mutex数组作为Mutex.WaitAll()方法的参数
            gMs[1] = gM2;
            Mutex.WaitAll(gMs);
//等待gM1和gM2都被释放
            Thread.Sleep(2000);
            Console.WriteLine(
"t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
            Event1.Set( );
//线程结束,将Event1设置为有信号状态
      }
     
public void t2Start( )
      {
            Console.WriteLine(
"t2Start started, gM1.WaitOne( )");
            gM1.WaitOne( );
//等待gM1的释放
            Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
            Event2.Set( );
//线程结束,将Event2设置为有信号状态
      }
     
public void t3Start( )
      {
            Console.WriteLine(
"t3Start started, Mutex.WaitAny(Mutex[])");
            Mutex[] gMs
= new Mutex[2];
            gMs[
0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
            gMs[1] = gM2;
            Mutex.WaitAny(gMs);
//等待数组中任意一个Mutex对象被释放
            Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
            Event3.Set( );
//线程结束,将Event3设置为有信号状态
      }
     
public void t4Start( )
      {
            Console.WriteLine(
"t4Start started, gM2.WaitOne( )");
            gM2.WaitOne( );
//等待gM2被释放
            Console.WriteLine("t4Start finished, gM2.WaitOne( )");
            Event4.Set( );
//线程结束,将Event4设置为有信号状态
      }
    }
}

 

摘自:http://www.cnblogs.com/xugang/archive/2008/04/06/1138856.html

相关文章:

开启一个新的终端并执行特定的命令

我的项目中有利用到远程控制&#xff0c;从windows端远程控制linux端&#xff0c;那么也就是接收远程的命令并在本机执行并返回结果。在父进程中用到popen()函数&#xff0c;popen()函数通过创建一个管道&#xff0c;调用fork()产生一个子进程&#xff0c;执行一个shell以运行命…

《评人工智能如何走向新阶段》后记(再续26)

427&#xff0c;SNN机理性测试 SNN利用时空处理&#xff0c;脉冲稀疏性和较高的内部神经元带宽来最大化神经形态计算的能量效率。尽管可以在这种情况下使用常规的基于硅的技术&#xff0c;但最终的神经元突触电路需要多个晶体管和复杂的布局&#xff0c;从而限制了集成密度。论…

Android5.1.1源码 - zygote fork出的子进程如何权限降级

前言 如果不知道zygote是什么&#xff0c;或者好奇zygote如何启动&#xff0c;可以去看老罗的文章&#xff1a; Android系统进程Zygote启动过程的源代码分析所有Android应用进程都是zygote fork出来的&#xff0c;新fork出来的应用进程还保持着root权限&#xff0c;这显然是不被…

system函数

转载自此处 相关函数 fork&#xff0c;execve&#xff0c;waitpid&#xff0c;popen 头文件#includ”stdlib.h” 定义函数 int system(const char * string); 函数说明 system()会调用fork()产生子进程&#xff0c;由子进程来调用/bin/sh-c string来执行参数string字符串…

《评人工智能如何走向新阶段》后记(再续27)

439&#xff0c;彩虹一号无人机实现人类永不落地的追求 日媒&#xff1a;中国亮出杀手锏 世界各国一直在研究提高飞机的续航能力 国内研制的彩虹一号无人机采用人工智能和其他高新技术&#xff0c;飞行高度30000米&#xff0c;并终于研制成功实现人类永不落地的追求。 440&a…

使用unix工具监控cpu、内存等系统资源占用率

1&#xff09;使用 sar -u 命令监控cpu使用$ sar -u 5 512:21:15 %usr %sys %wio %idle12:21:20 54 15 13 1912:21:25 41 18 15 2712:21:30 62 20 10 912:21:35 33 11 20 3612:21:40 38 13 17 31Average 45 15 15 24%usr&#xff0d;&#xff0d;运行在用户模式下cpu的使用百分…

C# 获取图片的EXIF 信息

关于 EXIF 信息的介绍。 1 EXIF&#xff0c;是英文Exchangeable Image File(可交换图像文件)的缩写。EXIF是一种图像文件格式&#xff0c;只是文件的后缀名为jpg。EXIF信息是由数码相机在拍摄过程中采集一系列的信息&#xff0c;然后把信息放置在jpg文件的头部&#xff0c;也就…

ffmpeg录屏/摄像头/指定窗口;别名alias设置

关于ffmpeg的使用方法很多&#xff0c;我简单写一下今天我捣鼓的几个。因为我的项目中要用到录屏和录制摄像头&#xff0c;所以试了下。网上关于录制指定窗口的方法并不多&#xff0c;我也是找了好久&#xff0c;试了好久才试出来的。 好了&#xff0c;废话不多说&#xff0c;…

黄聪:BackGroundWorker解决“线程间操作无效: 从不是创建控件的线程访问它” (C# VS2008)...

在编程中经常会遇到在一个按钮中执行复杂操作&#xff0c;并将复杂操作最后返回的值加入一个ListView或ComboBox中候选。这个时候程序会卡&#xff0c;当程序员将这些卡代码放进线程(Thread)中后发现当对控件操作时出现“线程间操作无效: 从不是创建控件的线程访问它”异常。 …

AWS 中国宁夏和北京区正式上线 Amazon SageMaker,中国用户终于能用到新工具和功能!

2020年 5 月 12 日&#xff0c;亚马逊云服务 Amazon Web Services, Inc. (AWS) 宣布&#xff0c;Amazon SageMaker 在由西云数据运营的 AWS 中国 (宁夏) 区域和光环新网运营的 AWS 中国&#xff08;北京&#xff09;区域正式上线。 Amazon SageMaker 在中国的上线使中国用户获…

Ubuntu Vim YouCompleteMe 安装

0. 必要工具安装 sudo apt-get install build-essential cmake 1. 安装 vundle mkdir ~/.vim/bundle git clone https://github.com/gmarik/vundle.git ~/.vim/bundle/vundle 2.编辑 .vimrc set nocompatible " be iMproved, required filetype off …

ubuntu vsftpd虚拟用户配置/ubuntu12.04上搭建vsftpd服务示例linux

转自这里 在ubuntu中安装完vsftpd后&#xff0c;安装libdb4.6-util: 复制代码 代码示例: sudo apt-get install db4.6-util 在etc下面建立目录vsftpd, 创建一个txt文档&#xff0c;比如logins.txt&#xff0c;在其中输入用户名及密码,如: 复制代码 代码示例: test 12345…

云从完成超过18亿元新一轮融资,加快上市步伐

近日&#xff0c;云从科技完成新一轮融资&#xff0c;总规模超过18亿元人民币&#xff0c;投资方除了中国互联网投资基金、上海国盛、广州南沙金控、长三角产业创新基金等政府基金外&#xff0c;还包括工商银行、海尔金控等产业战略投资者&#xff0c;进一步强化“AI国家队”的…

input core input.c (1)

drivers/input/input.c 就是所谓的input的核心程序。 分析这个文件&#xff0c;先从input_init开始。 1: static int __init input_init(void) 2: { 3: err class_register(&input_class); 4: err input_proc_init(); 5: err register_chrdev(INPUT_MAJOR, "i…

Swift解读专题四——字符串与字符

2019独角兽企业重金招聘Python工程师标准>>> Swift解读专题四——字符串与字符 一、引言 Swift中提供了String类型与Characters类型来处理字符串和字符数据&#xff0c;Swift中的String类型除了提供了许多方便开发者使用的方法外&#xff0c;还可以与Foundation框架…

非模态对话框的销毁

前面写过一篇关于模态和非模态对话框的文章&#xff0c;关于模态对话框和非模态对话框的创建、显示&#xff0c;以及和父对话框的传值 。文章末尾我遗留了一个问题&#xff0c;即当销毁非模态对话框后&#xff0c;需要置创建该非模态对话框的指针为NULL,但是具体是怎么操作呢&a…

LAMP兄弟连PHP课程学习笔记 第二天 PHP中使用变量

2019独角兽企业重金招聘Python工程师标准>>> 一、变量的介绍 变量&#xff1a;是指临时储存值的容器&#xff0c;这个值可以是数字或者文本或者其他组合。可以在程序使用的过程中更改。 二、变量的声明 1、如果用到的数据需要多次被调用时就声明为变量&#xff0c;P…

清华孵化,打造智能驾驶感控平台,超星未来获千万A轮融资

2020年5月14日&#xff0c;智能驾驶初创企业北京超星未来科技有限公司今天宣布已完成数千万元A轮融资&#xff0c;由恒大高科技领投&#xff0c;中关村前沿基金、和米资本、宝钜投资及图灵创投进行跟投。本轮融资将帮助超星未来在人才端和研发端持续投入&#xff0c;通过软硬件…

71 mac boook pro 无 gpu 下caffe 安装

71 mac boook pro 无 gpu 下caffe 安装 1.首先安装homebrew工具&#xff0c;相当于Mac下的yum或apt ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 2.安装Caffe依赖 $ brew install -vd snappy leveldb gflags glog s…

比较有趣的一个笔试题目

这2天忙于找工作&#xff0c;看.NET笔试题目的时&#xff0c;发现有些笔试题目还是比较有趣的&#xff0c;虽然考的都是基础&#xff0c;但是稍有不慎还是会进入圈套。以下面一个题目为例&#xff0c;听说是晕倒了一群人 - - classProgram { staticvoidMain(string[]…

82年 AI程序员征婚启示火了!年薪百万,女生神回复

最近在某社区&#xff0c;一则程序员征婚启示火了&#xff01;很多女生在评论区表示“全中”&#xff0c;想交流看看。然后评论区就炸了&#xff0c;有人恶意说yp&#xff0c;有人说看中了楼主的钱。笔者一翻&#xff0c;发现楼主果然无意中透露了百万年薪收入&#xff0c;虽然…

MFC给按钮添加皮肤

其实这也很简单&#xff0c;但是毕竟是因为我现在已经做出来了&#xff0c;当时还是查了一些资料的。废话不多说&#xff0c;直接上代码&#xff1a; 首先在类中声明&#xff1a; HBITMAP pausehBitmap; //用于暂停按钮的皮肤 然后将资源图片添加至资源视图中&#xff0…

Python全栈开发day2

1、python种类和区别 Cpython Python的官方版本&#xff0c;使用C语言实现&#xff0c;使用最为广泛&#xff0c;CPython实现会将源文件&#xff08;py文件&#xff09;转换成字节码文件&#xff08;pyc文件&#xff09;&#xff0c;然后运行在Python虚拟机上。 Jyhton Python的…

16分钟优化mRNA疫苗稳定性! 百度AI算法LinearDesign问世

新冠病毒全球累计确诊已超过400万人&#xff0c;疫苗是终结疫情的关键胜负手。在所有正在研发的疫苗路径中&#xff0c;研发速度更快、更具潜力的mRNA疫苗作为一种新兴技术受到了国内外的重点关注。但同时&#xff0c;mRNA疫苗由于稳定性不足容易在保存、运输中降解&#xff0c…

控制台打印汉字的方法

wstring gL"自动定理证明"; wstring sL"自动定理证明器"; EditDistance editdistance(g,s); //int distanceleneditdistance.CalEditDistance(); wcout.imbue(locale("chs")); set<string>words; editdistance.GetDictionary(words); O…

模态对话框的父窗口设置

我的毕业设计基本是搞完了&#xff0c;那么最近几天在做一些测试和修改。其中就有一个问题折腾了我好久&#xff0c;今天才发现&#xff0c;原来是这么回事。 我创建了一个非模态的子对话框&#xff0c;然后在这个子对话框中又创建了一个模态对话框&#xff0c;结果意外的是点…

XSD标准架构-----xsd:element 元素详解

声明一个元素。 <elementabstract Boolean : falseblock (#all | List of (extension | restriction | substitution))default stringfinal (#all | List of (extension | restriction))fixed stringform (qualified | unqualified)id IDmaxOccurs (nonNegativeInte…

VS2010使用Skin++

转载自http://blog.sina.com.cn/s/blog_5d23890b0100icjj.html。 Skin的使用 一.使用皮肤 将SkinPPWTL.lib 、skinppwtl.dll 、SkinPPWTL.h三个文件及相应皮肤&#xff08;**.ssk&#xff09;拷贝至工程文件夹下&#xff1b;一般还要将这些文件放在Debug文件夹下。 1.在(VC…

美翻朋友圈:用Python生成蒙太奇马赛克图片

题图 | 视觉中国来源 | ZackSock&#xff08;ID:ZackSock&#xff09;我们有时候会听到这么一个词--“蒙太奇”&#xff0c;但却不知道这个词是什么意思。蒙太奇原为建筑学术语&#xff0c;意为构成、装配。而后又延伸为一种剪辑理论&#xff1a;当不同镜头拼接在一起时&#x…

在SQL Server 2008中配置文件流(FILESTREAM)

SQL Server 2008推出了一个新的特性叫做文件流(FILESTREAM)&#xff0c;它使得基于SQL Server的应用程序可以在文件系统中存储非结构化的数据&#xff0c;例如文档、图片、音频、视频等等。文件流主要将SQL Server数据库引擎和新技术文件系统(NTFS)集成在一起;它主要以varbinar…