C#编程基础五

多线程

线程被定义为程序的执行路径。每个线程都定义了一个独特的控制流程。如果应用程序涉及复杂和耗时的操作,那么设置不同的执行路径或线程通常有助于每个线程执行特定的作业。

线程是轻量级的进程。使用线程的一个常见示例是通过现代操作系统实现并发编程。使用线程节省了CPU周期并提高了应用程序的效率。

到目前为止,我们编写了单个线程作为单个进程运行的程序,它是应用程序的运行实例。 但是,这样应用程序可以一次执行一个作业。为了使它一次执行多个任务,它可以分为较小的线程。

线程生命周期

当创建System.Threading.Thread类的对象时,线程的生命周期将会启动,当线程终止或完成执行时,该循环将结束。

以下是线程生命周期中的各种状态:

  • 未开始状态:线程实例创建但不调用Start方法的情况。
  • 就绪状态:线程准备运行并等待CPU周期时的情况。
  • 不可运行状态:线程不可执行,有以下几种情况:
    • Sleep方法已被调用
    • Wait方法已被调用
    • I/O操作阻止
  • 死亡状态:线程完成执行或中止时的情况。

主线程

在 C# 中,System.Threading.Thread类用于处理线程。它允许在多线程应用程序中创建和访问单个线程。在进程中执行的第一个线程称为主线程。

当 C# 程序开始执行时,主线程就会被自动创建。使用Thread类创建的线程称为主线程的子线程。可以使用Thread类的CurrentThread属性访问线程。

以下程序演示主线程执行:

using System;
using System.Threading;

namespace MultithreadingApplication
{
   class MainThreadProgram
   {
	  static void Main(string[] args)
	  {
		 Thread th = Thread.CurrentThread;
		 th.Name = "MainThread";
		 Console.WriteLine("This is {0}", th.Name);
		 Console.ReadKey();
	  }
   }
}

当上述代码被编译并执行时,它产生以下结果:

This is MainThread

Thread类的属性和方法

下表显示了Thread类的一些最常用的属性:

属性 描述
CurrentContext 获取当前正在执行的线程的上下文。
CurrentCulture 获取或设置当前线程的文化(culture)。
CurrentPrinciple 获取或设置线程的当前主体(用于基于角色的安全性)。
CurrentThread 获取当前正在运行的线程。
CurrentUICulture 获取或设置资源管理器使用的当前文化(culture),以便在运行时查找特定于文化的资源。
ExecutionContext 获取一个ExecutionContext对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive 获取指示当前线程的执行状态的值。
IsBackground 获取或设置一个值,指示线程是否是后台线程。
IsThreadPoolThread 获取一个值,指示线程是否属于托管线程池。
ManagedThreadId 获取当前受管线程的唯一标识符。
Name 获取或设置线程的名称。
Priority 获取或设置一个指示线程的调度优先级的值。
ThreadState 获取包含当前线程的状态的值。

下表显示了Thread类最常用的一些方法:

序号 方法 描述
1 public void Abort() 在调用它的线程中引发ThreadAbortException异常,以开始终止线程的进程。调用此方法通常会终止线程。
2 public static LocalDataStoreSlot AllocateDataSlot() 在所有线程上分配一个未命名的数据槽。为了获得更好的性能,请使用标记为ThreadStaticAttribute属性的字段。
3 public static LocalDataStoreSlot AllocateNamedDataSlot(string name) 在所有线程上分配一个命名的数据槽。为了获得更好的性能,请使用标记为ThreadStaticAttribute属性的字段。
4 public static void BeginCriticalRegion() 通知主机执行即将进入的代码区域,线程中止或未处理的异常的影响可能会危及应用程序域中的其他任务。
5 public static void BeginThreadAffinity() 通知托管代码即将执行依赖于当前物理操作系统线程标识的指令。
6 public static void EndCriticalRegion() 通知主机即将执行即将进入的代码区域,线程中止或未处理异常的影响限于当前任务。
7 public static void EndThreadAffinity() 通知托管代码已完成执行依赖于当前物理操作系统线程标识的指令的主机。
8 public static void FreeNamedDataSlot(string name) 消除进程中所有线程的名称和插槽之间的关联。为了获得更好的性能,请使用标记为ThreadStaticAttribute属性的字段。
9 public static Object GetData(LocalDataStoreSlot slot) 从当前线程的当前域中指定插槽中检索值。为了获得更好的性能,请使用标记为ThreadStaticAttribute属性的字段。
10 public static AppDomain GetDomain() 返回当前线程正在运行的当前域。
11 public static AppDomain GetDomainID() 返回唯一的应用程序域标识符
12 public static LocalDataStoreSlot GetNamedDataSlot(string name) 查找一个命名的数据槽。为了获得更好的性能,请使用标记为ThreadStaticAttribute属性的字段。
13 public void Interrupt() 中断处于WaitSleepJoin线程状态的线程。
14 public void Join() 阻止调用线程直到线程终止,同时继续执行标准COM和SendMessage抽取。此方法具有不同的重载形式。
15 public static void MemoryBarrier() 同步存储器访问如下:执行当前线程的处理器无法重新排序指令,使得在调用MemoryBarrier之前进行的存储器访问在内存访问之后执行,这些内存访问之后对MemoryBarrier的调用。
16 public static void ResetAbort() 取消当前线程中止请求。
17 public static void SetData(LocalDataStoreSlot slot, Object data) 为当前正在运行的线程的当前域设置指定槽中的数据。为了获得更好的性能,请改用标记为ThreadStaticAttribute属性的字段。
18 public void Start() 开始一个线程
19 public static void Sleep(int millisecondsTimeout) 使线程暂停一段时间
20 public static void SpinWait(int iterations) 使线程等待iterations参数定义的次数
21 public static byte VolatileRead(ref byte address),public static double VolatileRead(ref double address),public static int VolatileRead(ref int address),public static Object VolatileRead(ref Object address) 读取一个字段的值。该值是计算机中任何处理器写入的最新值,它不考虑处理器数量或处理器高速缓存的状态。此方法具有不同的重载形式。上面只给出了几个。
22 public static void VolatileWrite(ref byte address,byte value)public static void VolatileWrite(ref double address, double value)public static void VolatileWrite(ref int address, int value)public static void VolatileWrite(ref Object address, Object value) 立即将值写入字段,以便该值对计算机中的所有处理器可见。此方法具有不同的重载形式。上面只给出了几个。
23 public static bool Yield() 使调用线程对另一个准备在当前处理器上运行的线程执行执行。操作系统选择要产生的线程。

创建线程

实现线程是通过扩展Thread类创建的。扩展Thread类然后调用Start()方法来开始执行子线程。

以下程序演示了上面所说的概念:

using System;
using System.Threading;

namespace MultithreadingApplication
{
   class ThreadCreationProgram
   {
	  public static void CallToChildThread()
	  {
		 Console.WriteLine("Child thread starts");
	  }

	  static void Main(string[] args)
	  {
		 ThreadStart childref = new ThreadStart(CallToChildThread);
		 Console.WriteLine("In Main: Creating the Child thread");
		 Thread childThread = new Thread(childref);
		 childThread.Start();
		 Console.ReadKey();
	  }
   }
}

当上述代码被编译并执行时,它产生以下结果:

In Main: Creating the Child thread
Child thread starts

管理线程

Thread类提供了各种管理线程的方法。

以下示例演示了如何使用sleep()方法在特定时间段内暂停线程。

using System;
using System.Threading;

namespace MultithreadingApplication
{
   class ThreadCreationProgram
   {
	  public static void CallToChildThread()
	  {
		 Console.WriteLine("Child thread starts");

		 // the thread is paused for 5000 milliseconds
		 int sleepfor = 5000; 

		 Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
		 Thread.Sleep(sleepfor);
		 Console.WriteLine("Child thread resumes");
	  }

	  static void Main(string[] args)
	  {
		 ThreadStart childref = new ThreadStart(CallToChildThread);
		 Console.WriteLine("In Main: Creating the Child thread");
		 Thread childThread = new Thread(childref);
		 childThread.Start();
		 Console.ReadKey();
	  }
   }
}

当上述代码被编译并执行时,它产生以下结果:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

销毁线程

Abort()方法用于销毁线程。运行时通过抛出ThreadAbortException来中止线程。这个异常不能被捕获,控件发送到finally块(如果有的话)。

以下一个实现线程的程序:

using System;
using System.Threading;

namespace MultithreadingApplication
{
   class ThreadCreationProgram
   {
	  public static void CallToChildThread()
	  {
		 try
		 {
			Console.WriteLine("Child thread starts");

			// do some work, like counting to 10
			for (int counter = 0; counter <= 10; counter++)
			{
			   Thread.Sleep(500);
			   Console.WriteLine(counter);
			}

			Console.WriteLine("Child Thread Completed");
		 }

		 catch (ThreadAbortException e)
		 {
			Console.WriteLine("Thread Abort Exception");
		 }
		 finally
		 {
			Console.WriteLine("Couldn't catch the Thread Exception");
		 }
	  }

	  static void Main(string[] args)
	  {
		 ThreadStart childref = new ThreadStart(CallToChildThread);
		 Console.WriteLine("In Main: Creating the Child thread");
		 Thread childThread = new Thread(childref);
		 childThread.Start();

		 //stop the main thread for some time
		 Thread.Sleep(2000);

		 //now abort the child
		 Console.WriteLine("In Main: Aborting the Child thread");

		 childThread.Abort();
		 Console.ReadKey();
	  }
   }
}

当上述代码被编译并执行时,它产生以下结果:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception

多线程生命周期

在 C# 中,每个线程都有一个生命周期。线程的生命周期是在创建System.Threading.Thread类的实例时启动的。当线程的任务执行完成时,线程的生命周期结束。

C# 中线程的生命周期中有以下状态。

  • 未开始
  • Runnable(准备运行)
  • 运行
  • 不可运行
  • 死亡(终止)

未开始

当Thread类的实例被创建时,默认情况下它处于未启动状态。

可运行状态

当线程上的start()方法被调用时,它处于可运行状态或准备运行状态。

运行状态

一个进程中只能执行一个线程。在执行时,线程处于运行状态。

不可运行状态

线程处于不可运行状态,如果在线程上调用了sleep()或wait()方法,或者输入/输出操作被阻止。

死亡状态

完成任务后,线程进入死亡或终止状态。

Thread

C#Thread类提供了创建和控制线程的属性和方法。它在System.Threading命名空间中定义。

C# Thread属性

Thread类中一些的重要属性如下:

属性 描述
CurrentThread 返回当前正在运行的线程的实例。
IsAlive 检查当前线程是否存活,它用于查找线程的执行状态。
IsBackground 用于获取或设置当前线程是否在后台的值。
ManagedThreadId 用于获取当前受管线程的唯一ID。
Name 用于获取或设置当前线程的名称。
Priority 用于获取或设置当前线程的优先级。
ThreadState 用于返回表示线程状态的值。

C# Thread方法

Thread类中定义的一些重要方法如下:

方法 描述
Abort() 用于终止线程,它引发ThreadAbortException异常。
Interrupt() 用于中断处于WaitSleepJoin状态的线程。
Join() 用于阻止所有调用线程,直到此线程终止。
ResetAbort() 用于取消当前线程的中止请求。
Resume() 用于恢复挂起的线程。
Sleep(Int32) 用于按指定的毫秒暂停当前线程。
Start() 将线程的当前状态更改为Runnable
Suspend() 如果它不被挂起则暂停当前线程。
Yield() 用于使当前线程执行到另一个线程。

主线程

C#Thread类提供了创建和控制线程的属性和方法。它在System.Threading命名空间中定义。

C# Thread属性

Thread类中一些的重要属性如下:

属性 描述
CurrentThread 返回当前正在运行的线程的实例。
IsAlive 检查当前线程是否存活,它用于查找线程的执行状态。
IsBackground 用于获取或设置当前线程是否在后台的值。
ManagedThreadId 用于获取当前受管线程的唯一ID。
Name 用于获取或设置当前线程的名称。
Priority 用于获取或设置当前线程的优先级。
ThreadState 用于返回表示线程状态的值。

C# Thread方法

Thread类中定义的一些重要方法如下:

方法 描述
Abort() 用于终止线程,它引发ThreadAbortException异常。
Interrupt() 用于中断处于WaitSleepJoin状态的线程。
Join() 用于阻止所有调用线程,直到此线程终止。
ResetAbort() 用于取消当前线程的中止请求。
Resume() 用于恢复挂起的线程。
Sleep(Int32) 用于按指定的毫秒暂停当前线程。
Start() 将线程的当前状态更改为Runnable
Suspend() 如果它不被挂起则暂停当前线程。
Yield() 用于使当前线程执行到另一个线程。

线程实例

C#Thread类提供了创建和控制线程的属性和方法。它在System.Threading命名空间中定义。

C# Thread属性

Thread类中一些的重要属性如下:

属性 描述
CurrentThread 返回当前正在运行的线程的实例。
IsAlive 检查当前线程是否存活,它用于查找线程的执行状态。
IsBackground 用于获取或设置当前线程是否在后台的值。
ManagedThreadId 用于获取当前受管线程的唯一ID。
Name 用于获取或设置当前线程的名称。
Priority 用于获取或设置当前线程的优先级。
ThreadState 用于返回表示线程状态的值。

C# Thread方法

Thread类中定义的一些重要方法如下:

方法 描述
Abort() 用于终止线程,它引发ThreadAbortException异常。
Interrupt() 用于中断处于WaitSleepJoin状态的线程。
Join() 用于阻止所有调用线程,直到此线程终止。
ResetAbort() 用于取消当前线程的中止请求。
Resume() 用于恢复挂起的线程。
Sleep(Int32) 用于按指定的毫秒暂停当前线程。
Start() 将线程的当前状态更改为Runnable
Suspend() 如果它不被挂起则暂停当前线程。
Yield() 用于使当前线程执行到另一个线程。

线程实例:Sleep()方法

在执行的线程上调用Sleep()方法来指定的毫秒暂停当前线程。从面使其他线程有机会开始执行。

using System;
using System.Threading;
public class MyThread
{
	public void Thread1()
	{
		for (int i = 0; i < 10; i++)
		{
			String curTime = DateTime.Now.ToString();
			Console.WriteLine("Thread1 at "+ curTime + " => "+ i);
			Thread.Sleep(500);// 挂起半秒
		}
	}
}
public class ThreadExample
{
	public static void Main()
	{
		MyThread mt = new MyThread();
		Thread t1 = new Thread(new ThreadStart(mt.Thread1));
		Thread t2 = new Thread(new ThreadStart(mt.Thread1));
		t1.Start();
		t2.Start();
	}
}

执行上面示例代码,得到以下结果 -

Thread1 at 2017/9/13 3:24:42 => 0
Thread1 at 2017/9/13 3:24:42 => 0
Thread1 at 2017/9/13 3:24:42 => 1
Thread1 at 2017/9/13 3:24:42 => 1
Thread1 at 2017/9/13 3:24:43 => 2
Thread1 at 2017/9/13 3:24:43 => 2
Thread1 at 2017/9/13 3:24:43 => 3
Thread1 at 2017/9/13 3:24:43 => 3
Thread1 at 2017/9/13 3:24:44 => 4
Thread1 at 2017/9/13 3:24:44 => 4
Thread1 at 2017/9/13 3:24:44 => 5
Thread1 at 2017/9/13 3:24:44 => 5
Thread1 at 2017/9/13 3:24:45 => 6
Thread1 at 2017/9/13 3:24:45 => 6
Thread1 at 2017/9/13 3:24:45 => 7
Thread1 at 2017/9/13 3:24:45 => 7
Thread1 at 2017/9/13 3:24:46 => 8
Thread1 at 2017/9/13 3:24:46 => 8
Thread1 at 2017/9/13 3:24:46 => 9
Thread1 at 2017/9/13 3:24:46 => 9

线程实例:Abort()方法

在C#中,Thread类的Abort()方法用于终止线程。如果未完成中止操作,它会引发ThreadAbortException异常。

using System;
using System.Threading;
public class MyThread
{
	public void Thread1()
	{
		for (int i = 0; i < 10; i++)
		{
			Console.WriteLine(i);
			Thread.Sleep(200);
		}
	}
}
public class ThreadExample
{
	public static void Main()
	{
		Console.WriteLine("Start of Main");
		MyThread mt = new MyThread();
		Thread t1 = new Thread(new ThreadStart(mt.Thread1));
		Thread t2 = new Thread(new ThreadStart(mt.Thread1));

		t1.Start();
		t2.Start();
		try
		{
			t1.Abort();
			t2.Abort();
		}
		catch (ThreadAbortException tae)
		{
			Console.WriteLine(tae.ToString());
		}
		Console.WriteLine("End of Main");
	}
}

执行上面示例代码,得到以下结果(输出是不可预测的,因为线程可能处于运行状态。) -

Start of Main
0
End of Main

线程实例:Join()方法

Join()方法使所有的调用线程等待直到当前线程(已连接线程)终止或完成其任务。

参考以下示例 -

using System;
using System.Threading;
public class MyThread
{
	public void Thread1()
	{
		for (int i = 0; i < 5; i++)
		{
			Console.WriteLine("Thread1 : "+i);
			Thread.Sleep(500);
		}
	}
}
public class ThreadExample
{
	public static void Main()
	{
		MyThread mt = new MyThread();
		Thread t1 = new Thread(new ThreadStart(mt.Thread1));
		Thread t2 = new Thread(new ThreadStart(mt.Thread1));
		Thread t3 = new Thread(new ThreadStart(mt.Thread1));
		t1.Start();
		t1.Join();
		t2.Start();
		t3.Start();
	}
}

执行上面示例代码,得到以下结果 -

Thread1 : 0
Thread1 : 1
Thread1 : 2
Thread1 : 3
Thread1 : 4
Thread1 : 0
Thread1 : 0
Thread1 : 1
Thread1 : 1
Thread1 : 2
Thread1 : 2
Thread1 : 3
Thread1 : 3
Thread1 : 4
Thread1 : 4

线程命名实例

使用Thread类的Name属性来更改或获取线程的名称。下面我们来看一个例子,演示如何设置和获取线程的名称。

using System;
using System.Threading;

public class MyThread
{
	public void Thread1()
	{
		for(int i=0; i<5; i++)
		{
			Thread t = Thread.CurrentThread;
			Console.WriteLine(t.Name + " is running at :"+i);
		}
	}
}
public class ThreadExample
{
	public static void Main()
	{
		MyThread mt = new MyThread();
		Thread t1 = new Thread(new ThreadStart(mt.Thread1));
		Thread t2 = new Thread(new ThreadStart(mt.Thread1));
		Thread t3 = new Thread(new ThreadStart(mt.Thread1));
		t1.Name = "Thread1";
		t2.Name = "Thread2";
		t3.Name = "Thread3";
		t1.Start();
		t2.Start();
		t3.Start();
	}
}

执行上面示例代码,得到以下结果 -

Thread1 is running at :0
Thread2 is running at :0
Thread3 is running at :0
Thread3 is running at :1
Thread3 is running at :2
Thread3 is running at :3
Thread3 is running at :4
Thread2 is running at :1
Thread2 is running at :2
Thread2 is running at :3
Thread2 is running at :4
Thread1 is running at :1
Thread1 is running at :2
Thread1 is running at :3
Thread1 is running at :4

线程示例:优先级

我们来看下面的一个例子,演示如何改变线程的优先级。高优先级线程优先执行。但是不能完全保证,因为线程是高度依赖系统的。它只提高了优先级较高的线程在低优先级线程之前执行的机会。

using System;
using System.Threading;
public class MyThread
{
	public void Thread1()
	{
		for(int i=0; i<3; i++)
		{
			Thread t = Thread.CurrentThread;
			Console.WriteLine(t.Name + " is running");
		}


	}
}
public class ThreadExample
{
	public static void Main()
	{
		MyThread mt = new MyThread();
		Thread t1 = new Thread(new ThreadStart(mt.Thread1));
		Thread t2 = new Thread(new ThreadStart(mt.Thread1));
		Thread t3 = new Thread(new ThreadStart(mt.Thread1));
		t1.Name = "Highest-Thread ";
		t2.Name = "Normal-Thread ";
		t3.Name = "Lowest-Thread ";
		t1.Priority = ThreadPriority.Highest;
		t2.Priority = ThreadPriority.Normal;
		t3.Priority = ThreadPriority.Lowest;

		t1.Start();
		t2.Start();
		t3.Start();
	}
}

输出是不可预测的,因为线程系统依赖性高。它可以遵循任何算法抢先或非抢占式来执行。

执行上面示例代码,得到以下结果 -

Highest-Thread  is running
Normal-Thread  is running
Normal-Thread  is running
Normal-Thread  is running
Lowest-Thread  is running
Lowest-Thread  is running
Lowest-Thread  is running
Highest-Thread  is running
Highest-Thread  is running

线程同步

同步是一种只允许一个线程在特定时间访问某些资源的技术。没有其他线程可以中断,直到所分配的线程或当前访问线程访问数据完成其任务。

在多线程程序中,允许线程访问任何资源所需的执行时间。线程共享资源并异步执行。 访问共享资源(数据)是有时可能会暂停系统的关键任务。所以可以通过线程同步来处理它。

主要场景如:存款,取款等交易业务处理。

线程同步的优点

  • 一致性维护
  • 无线程干扰

C#锁定

使用 C#lock关键字同步执行程序。它用于为当前线程锁定,执行任务,然后释放锁定。它确保其他线程在执行完成之前不会中断执行。

下面,创建两个非同步和同步的例子。

C# 示例:非同步

在这个例子中,我们不使用锁。此示例异步执行。换句话说,线程之间存在上下文切换。

using System;
using System.Threading;
class Printer
{
	public void PrintTable()
	{
		for (int i = 1; i <= 5; i++)
		{
			Thread t = Thread.CurrentThread;
			Thread.Sleep(200);
			Console.WriteLine(t.Name+" "+i);
		}
	}
}
class Program
{
	public static void Main(string[] args)
	{
		Printer p = new Printer();
		Thread t1 = new Thread(new ThreadStart(p.PrintTable));
		Thread t2 = new Thread(new ThreadStart(p.PrintTable));
		t1.Name = "Thread 1 :";
		t2.Name = "Thread 2 :";
		t1.Start();
		t2.Start();
	}
}

执行上面示例代码,可以看到以下输出结果 -

Thread 2 : 1
Thread 1 : 1
Thread 2 : 2
Thread 1 : 2
Thread 2 : 3
Thread 1 : 3
Thread 2 : 4
Thread 1 : 4
Thread 2 : 5
Thread 1 : 5

C# 线程同步示例

在这个例子中,我们使用lock块,因此示例同步执行。 换句话说,线程之间没有上下文切换。在输出部分,可以看到第二个线程在第一个线程完成任务之后开始执行。

using System;
using System.Threading;
class Printer
{
	public void PrintTable()
	{
		lock (this)
		{
			for (int i = 1; i <= 5; i++)
			{
				Thread t = Thread.CurrentThread;
				Thread.Sleep(100);
				Console.WriteLine(t.Name + " " + i);
			}
		}
	}
}
class Program
{
	public static void Main(string[] args)
	{
		Printer p = new Printer();
		Thread t1 = new Thread(new ThreadStart(p.PrintTable));
		Thread t2 = new Thread(new ThreadStart(p.PrintTable));
		t1.Name = "Thread 1 :";
		t2.Name = "Thread 2 :";
		t1.Start();
		t2.Start();
	}
}

执行上面示例代码,可以看到以下输出结果 -

Thread 1 : 1
Thread 1 : 2
Thread 1 : 3
Thread 1 : 4
Thread 1 : 5
Thread 2 : 1
Thread 2 : 2
Thread 2 : 3
Thread 2 : 4
Thread 2 : 5
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇