当前位置: 首页 > news >正文

藁城网站建设哪家好又一个wordpress网站

藁城网站建设哪家好,又一个wordpress网站,wordpress建立一个页面模板下载,2017做那个网站能致富目录 C#多线程编程系列#xff08;二#xff09;- 线程基础 1.1 简介1.2 创建线程1.3 暂停线程1.4 线程等待1.5 终止线程1.6 检测线程状态1.7 线程优先级1.8 前台线程和后台线程1.9 向线程传递参数1.10 C# Lock关键字的使用1.11 使用Monitor类锁定资源1.12 多线程中处理异常…目录 C#多线程编程系列二- 线程基础 1.1 简介1.2 创建线程1.3 暂停线程1.4 线程等待1.5 终止线程1.6 检测线程状态1.7 线程优先级1.8 前台线程和后台线程1.9 向线程传递参数1.10 C# Lock关键字的使用1.11 使用Monitor类锁定资源1.12 多线程中处理异常参考书籍笔者水平有限如果错误欢迎各位批评指正 C#多线程编程系列二- 线程基础 1.1 简介# 线程基础主要包括线程创建、挂起、等待和终止线程。关于更多的线程的底层实现CPU时间片轮转等等的知识可以参考《深入理解计算机系统》一书中关于进程和线程的章节本文不过多赘述。 1.2 创建线程# 在C#语言中创建线程是一件非常简单的事情它只需要用到 System.Threading命名空间其中主要使用Thread类来创建线程。 演示代码如下所示 Copy using System; using System.Threading; // 创建线程需要用到的命名空间 namespace Recipe1 { class Program { static void Main(string[] args) { // 1.创建一个线程 PrintNumbers为该线程所需要执行的方法 Thread t new Thread(PrintNumbers); // 2.启动线程 t.Start(); // 主线程也运行PrintNumbers方法方便对照 PrintNumbers(); // 暂停一下 Console.ReadKey(); } static void PrintNumbers() { // 使用Thread.CurrentThread.ManagedThreadId 可以获取当前运行线程的唯一标识通过它来区别线程 Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 开始打印...); for (int i 0; i 10; i) { Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 打印:{i}); } } } } 运行结果如下图所示我们可以通过运行结果得知上面的代码创建了一个线程然后主线程和创建的线程交叉输出结果这说明PrintNumbers方法同时运行在主线程和另外一个线程中。 1.3 暂停线程# 暂停线程这里使用的方式是通过Thread.Sleep方法如果线程执行Thread.Sleep方法那么操作系统将在指定的时间内不为该线程分配任何时间片。如果Sleep时间100ms那么操作系统将至少让该线程睡眠100ms或者更长时间所以Thread.Sleep方法不能作为高精度的计时器使用。 演示代码如下所示 Copy using System; using System.Threading; // 创建线程需要用到的命名空间 namespace Recipe2 { class Program { static void Main(string[] args) { // 1.创建一个线程 PrintNumbers为该线程所需要执行的方法 Thread t new Thread(PrintNumbersWithDelay); // 2.启动线程 t.Start(); // 暂停一下 Console.ReadKey(); } static void PrintNumbersWithDelay() { Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}); for (int i 0; i 10; i) { //3. 使用Thread.Sleep方法来使当前线程睡眠TimeSpan.FromSeconds(2)表示时间为 2秒 Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}); } } } } 运行结果如下图所示通过下图可以确定上面的代码是有效的通过Thread.Sleep方法使线程休眠了2秒左右但是并不是特别精确的2秒。验证了上面的说法它的睡眠是至少让线程睡眠多长时间而不是一定多长时间。 1.4 线程等待# 在本章中线程等待使用的是Join方法该方法将暂停执行当前线程直到所等待的另一个线程终止。在简单的线程同步中会使用到但它比较简单不作过多介绍。 演示代码如下所示 Copy class Program { static void Main(string[] args) { Console.WriteLine($-------开始执行 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}-------); // 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法 Thread t new Thread(PrintNumbersWithDelay); // 2.启动线程 t.Start(); // 3.等待线程结束 t.Join(); Console.WriteLine($-------执行完毕 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}-------); // 暂停一下 Console.ReadKey(); } static void PrintNumbersWithDelay() { Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}); for (int i 0; i 10; i) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}); } } } 运行结果如下图所示开始执行和执行完毕两条信息由主线程打印根据其输出的顺序可见主线程是等待另外的线程结束后才输出执行完毕这条信息。 1.5 终止线程# 终止线程使用的方法是Abort方法当该方法被执行时将尝试销毁该线程。通过引发ThreadAbortException异常使线程被销毁。但一般不推荐使用该方法原因有以下几点。 使用Abort方法只是尝试销毁该线程但不一定能终止线程。如果被终止的线程在执行lock内的代码那么终止线程会造成线程不安全。线程终止时CLR会保证自己内部的数据结构不会损坏但是BCL不能保证。基于以上原因不推荐使用Abort方法在实际项目中一般使用CancellationToken来终止线程。 演示代码如下所示 Copy static void Main(string[] args) { Console.WriteLine($-------开始执行 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}-------); // 1.创建一个线程 PrintNumbersWithDelay为该线程所需要执行的方法 Thread t new Thread(PrintNumbersWithDelay); // 2.启动线程 t.Start(); // 3.主线程休眠6秒 Thread.Sleep(TimeSpan.FromSeconds(6)); // 4.终止线程 t.Abort(); Console.WriteLine($-------执行完毕 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}-------); // 暂停一下 Console.ReadKey(); } static void PrintNumbersWithDelay() { Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 开始打印... 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}); for (int i 0; i 10; i) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine($线程{Thread.CurrentThread.ManagedThreadId} 打印:{i} 现在时间{DateTime.Now.ToString(HH:mm:ss.ffff)}); } } 运行结果如下图所示启动所创建的线程3后6秒钟主线程调用了Abort方法线程3没有继续执行便结束了与预期的结果一致。 1.6 检测线程状态# 线程的状态可通过访问ThreadState属性来检测ThreadState是一个枚举类型一共有10种状态状态具体含义如下表所示。 成员名称说明Aborted线程处于 Stopped 状态中。AbortRequested已对线程调用了 Thread.Abort 方法但线程尚未收到试图终止它的挂起的 System.Threading.ThreadAbortException。Background线程正作为后台线程执行相对于前台线程而言。此状态可以通过设置 Thread.IsBackground 属性来控制。Running线程已启动它未被阻塞并且没有挂起的 ThreadAbortException。Stopped线程已停止。StopRequested正在请求线程停止。这仅用于内部。Suspended线程已挂起。SuspendRequested正在请求线程挂起。Unstarted尚未对线程调用 Thread.Start 方法。WaitSleepJoin由于调用 Wait、Sleep 或 Join线程已被阻止。 下表列出导致状态更改的操作。 操作ThreadState在公共语言运行库中创建线程。Unstarted线程调用 StartUnstarted线程开始运行。Running线程调用 SleepWaitSleepJoin线程对其他对象调用 Wait。WaitSleepJoin线程对其他线程调用 Join。WaitSleepJoin另一个线程调用 InterruptRunning另一个线程调用 SuspendSuspendRequested线程响应 Suspend 请求。Suspended另一个线程调用 ResumeRunning另一个线程调用 AbortAbortRequested线程响应 Abort 请求。Stopped线程被终止。Stopped 演示代码如下所示 Copy static void Main(string[] args) { Console.WriteLine(开始执行...); Thread t new Thread(PrintNumbersWithStatus); Thread t2 new Thread(DoNothing); // 使用ThreadState查看线程状态 此时线程未启动应为Unstarted Console.WriteLine($Check 1 :{t.ThreadState}); t2.Start(); t.Start(); // 线程启动 状态应为 Running Console.WriteLine($Check 2 :{t.ThreadState}); // 由于PrintNumberWithStatus方法开始执行状态为Running // 但是经接着会执行Thread.Sleep方法 状态会转为 WaitSleepJoin for (int i 1; i 30; i) { Console.WriteLine($Check 3 : {t.ThreadState}); } // 延时一段时间方便查看状态 Thread.Sleep(TimeSpan.FromSeconds(6)); // 终止线程 t.Abort(); Console.WriteLine(t线程被终止); // 由于该线程是被Abort方法终止 所以状态为 Aborted或AbortRequested Console.WriteLine($Check 4 : {t.ThreadState}); // 该线程正常执行结束 所以状态为Stopped Console.WriteLine($Check 5 : {t2.ThreadState}); Console.ReadKey(); } static void DoNothing() { Thread.Sleep(TimeSpan.FromSeconds(2)); } static void PrintNumbersWithStatus() { Console.WriteLine(t线程开始执行...); // 在线程内部可通过Thread.CurrentThread拿到当前线程Thread对象 Console.WriteLine($Check 6 : {Thread.CurrentThread.ThreadState}); for (int i 1; i 10; i) { Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine($t线程输出 {i}); } } 运行结果如下图所示与预期的结果一致。 1.7 线程优先级# Windows操作系统为抢占式多线程(Preemptive multithreaded)操作系统是因为线程可在任何时间停止被枪占并调度另一个线程。 Windows操作系统中线程有0(最低) ~ 31(最高)的优先级而优先级越高所能占用的CPU时间就越多确定某个线程所处的优先级需要考虑进程优先级和相对线程优先级两个优先级。 进程优先级Windows支持6个进程优先级分别是Idle、Below Normal、Normal、Above normal、High 和Realtime。默认为Normal。相对线程优先级相对线程优先级是相对于进程优先级的因为进程包含了线程。Windows支持7个相对线程优先级分别是Idle、Lowest、Below Normal、Normal、Above Normal、Highest 和 Time-Critical.默认为Normal。下表总结了进程的优先级和线程的相对优先级与优先级(0~31)的映射关系。粗体为相对线程优先级斜体为进程优先级。 IdleBelow NormalNormalAbove NormalHighRealtimeTime-Critical151515151531Highest6810121526Above Normal579111425Normal468101324Below Normal35791223Lowest24681122Idle1111116 而在C#程序中可更改线程的相对优先级需要设置Thread的Priority属性可设置为ThreadPriority枚举类型的五个值之一Lowest、BelowNormal、Normal、AboveNormal 或 Highest。CLR为自己保留了Idle和Time-Critical优先级程序中不可设置。 演示代码如下所示。 Copy static void Main(string[] args) { Console.WriteLine($当前线程优先级: {Thread.CurrentThread.Priority} \r\n); // 第一次测试在所有核心上运行 Console.WriteLine(运行在所有空闲的核心上); RunThreads(); Thread.Sleep(TimeSpan.FromSeconds(2)); // 第二次测试在单个核心上运行 Console.WriteLine(\r\n运行在单个核心上); // 设置在单个核心上运行 System.Diagnostics.Process.GetCurrentProcess().ProcessorAffinity new IntPtr(1); RunThreads(); Console.ReadLine(); } static void RunThreads() { var sample new ThreadSample(); var threadOne new Thread(sample.CountNumbers); threadOne.Name 线程一; var threadTwo new Thread(sample.CountNumbers); threadTwo.Name 线程二; // 设置优先级和启动线程 threadOne.Priority ThreadPriority.Highest; threadTwo.Priority ThreadPriority.Lowest; threadOne.Start(); threadTwo.Start(); // 延时2秒 查看结果 Thread.Sleep(TimeSpan.FromSeconds(2)); sample.Stop(); } class ThreadSample { private bool _isStopped false; public void Stop() { _isStopped true; } public void CountNumbers() { long counter 0; while (!_isStopped) { counter; } Console.WriteLine(${Thread.CurrentThread.Name} 优先级为 {Thread.CurrentThread.Priority,11} 计数为 {counter,13:N0}); } } 运行结果如下图所示。Highest占用的CPU时间明显多于Lowest。当程序运行在所有核心上时线程可以在不同核心同时运行所以Highest和Lowest差距会小一些。 1.8 前台线程和后台线程# 在CLR中线程要么是前台线程要么就是后台线程。当一个进程的所有前台线程停止运行时CLR将强制终止仍在运行的任何后台线程不会抛出异常。 在C#中可通过Thread类中的IsBackground属性来指定是否为后台线程。在线程生命周期中任何时候都可从前台线程变为后台线程。线程池中的线程默认为后台线程。 演示代码如下所示。 Copy static void Main(string[] args) { var sampleForeground new ThreadSample(10); var sampleBackground new ThreadSample(20); var threadPoolBackground new ThreadSample(20); // 默认创建为前台线程 var threadOne new Thread(sampleForeground.CountNumbers); threadOne.Name 前台线程; var threadTwo new Thread(sampleBackground.CountNumbers); threadTwo.Name 后台线程; // 设置IsBackground属性为 true 表示后台线程 threadTwo.IsBackground true; // 线程池内的线程默认为 后台线程 ThreadPool.QueueUserWorkItem((obj) { Thread.CurrentThread.Name 线程池线程; threadPoolBackground.CountNumbers(); }); // 启动线程 threadOne.Start(); threadTwo.Start(); } class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations iterations; } public void CountNumbers() { for (int i 0; i _iterations; i) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(${Thread.CurrentThread.Name} prints {i}); } } } 运行结果如下图所示。当前台线程10次循环结束以后创建的后台线程和线程池线程都会被CLR强制结束。 1.9 向线程传递参数# 向线程中传递参数常用的有三种方法构造函数传值、Start方法传值和Lambda表达式传值一般常用Start方法来传值。 演示代码如下所示通过三种方式来传递参数告诉线程中的循环最终需要循环几次。 Copy static void Main(string[] args) { // 第一种方法 通过构造函数传值 var sample new ThreadSample(10); var threadOne new Thread(sample.CountNumbers); threadOne.Name ThreadOne; threadOne.Start(); threadOne.Join(); Console.WriteLine(--------------------------); // 第二种方法 使用Start方法传值 // Count方法 接收一个Object类型参数 var threadTwo new Thread(Count); threadTwo.Name ThreadTwo; // Start方法中传入的值 会传递到 Count方法 Object参数上 threadTwo.Start(8); threadTwo.Join(); Console.WriteLine(--------------------------); // 第三种方法 Lambda表达式传值 // 实际上是构建了一个匿名函数 通过函数闭包来传值 var threadThree new Thread(() CountNumbers(12)); threadThree.Name ThreadThree; threadThree.Start(); threadThree.Join(); Console.WriteLine(--------------------------); // Lambda表达式传值 会共享变量值 int i 10; var threadFour new Thread(() PrintNumber(i)); i 20; var threadFive new Thread(() PrintNumber(i)); threadFour.Start(); threadFive.Start(); } static void Count(object iterations) { CountNumbers((int)iterations); } static void CountNumbers(int iterations) { for (int i 1; i iterations; i) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(${Thread.CurrentThread.Name} prints {i}); } } static void PrintNumber(int number) { Console.WriteLine(number); } class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations iterations; } public void CountNumbers() { for (int i 1; i _iterations; i) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine(${Thread.CurrentThread.Name} prints {i}); } } } 运行结果如下图所示与预期结果相符。 1.10 C# Lock关键字的使用# 在多线程的系统中由于CPU的时间片轮转等线程调度算法的使用容易出现线程安全问题。具体可参考《深入理解计算机系统》一书相关的章节。 在C#中lock关键字是一个语法糖它将Monitor封装给object加上一个互斥锁从而实现代码的线程安全Monitor会在下一节中介绍。 对于lock关键字还是Monitor锁定的对象都必须小心选择不恰当的选择可能会造成严重的性能问题甚至发生死锁。以下有几条关于选择锁定对象的建议。 同步锁定的对象不能是值类型。因为使用值类型时会有装箱的问题装箱后的就成了一个新的实例会导致Monitor.Enter()和Monitor.Exit()接收到不同的实例而失去关联性避免锁定this、typeof(type)和string。this和typeof(type)锁定可能在其它不相干的代码中会有相同的定义导致多个同步块互相阻塞。string需要考虑字符串拘留的问题如果同一个字符串常量在多个地方出现可能引用的会是同一个实例。对象的选择作用域尽可能刚好达到要求使用静态的、私有的变量。以下演示代码实现了多线程情况下的计数功能一种实现是线程不安全的会导致结果与预期不相符但也有可能正确。另外一种使用了lock关键字进行线程同步所以它结果是一定的。 Copy static void Main(string[] args) { Console.WriteLine(错误的多线程计数方式); var c new Counter(); // 开启3个线程使用没有同步块的计数方式对其进行计数 var t1 new Thread(() TestCounter(c)); var t2 new Thread(() TestCounter(c)); var t3 new Thread(() TestCounter(c)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); // 因为多线程 线程抢占等原因 其结果是不一定的 碰巧可能为0 Console.WriteLine($Total count: {c.Count}); Console.WriteLine(--------------------------); Console.WriteLine(正确的多线程计数方式); var c1 new CounterWithLock(); // 开启3个线程使用带有lock同步块的方式对其进行计数 t1 new Thread(() TestCounter(c1)); t2 new Thread(() TestCounter(c1)); t3 new Thread(() TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); // 其结果是一定的 为0 Console.WriteLine($Total count: {c1.Count}); Console.ReadLine(); } static void TestCounter(CounterBase c) { for (int i 0; i 100000; i) { c.Increment(); c.Decrement(); } } // 线程不安全的计数 class Counter : CounterBase { public int Count { get; private set; } public override void Increment() { Count; } public override void Decrement() { Count--; } } // 线程安全的计数 class CounterWithLock : CounterBase { private readonly object _syncRoot new Object(); public int Count { get; private set; } public override void Increment() { // 使用Lock关键字 锁定私有变量 lock (_syncRoot) { // 同步块 Count; } } public override void Decrement() { lock (_syncRoot) { Count--; } } } abstract class CounterBase { public abstract void Increment(); public abstract void Decrement(); } 运行结果如下图所示与预期结果相符。 1.11 使用Monitor类锁定资源# Monitor类主要用于线程同步中 lock关键字是对Monitor类的一个封装其封装结构如下代码所示。 Copy try { Monitor.Enter(obj); dosomething(); } catch(Exception ex) { } finally { Monitor.Exit(obj); } 以下代码演示了使用Monitor.TyeEnter()方法避免资源死锁和使用lock发生资源死锁的场景。 Copy static void Main(string[] args) { object lock1 new object(); object lock2 new object(); new Thread(() LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Thread.Sleep(1000); Console.WriteLine(Monitor.TryEnter可以不被阻塞, 在超过指定时间后返回false); // 如果5S不能进入同步块那么返回。 // 因为前面的lock锁定了 lock2变量 而LockTooMuch()一开始锁定了lock1 所以这个同步块无法获取 lock1 而LockTooMuch方法内也不能获取lock2 // 只能等待TryEnter超时 释放 lock2 LockTooMuch()才会是释放 lock1 if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))) { Console.WriteLine(获取保护资源成功); } else { Console.WriteLine(获取资源超时); } } new Thread(() LockTooMuch(lock1, lock2)).Start(); Console.WriteLine(----------------------------------); lock (lock2) { Console.WriteLine(这里会发生资源死锁); Thread.Sleep(1000); // 这里必然会发生死锁 // 本同步块 锁定了 lock2 无法得到 lock1 // 而 LockTooMuch 锁定了 lock1 无法得到 lock2 lock (lock1) { // 该语句永远都不会执行 Console.WriteLine(获取保护资源成功); } } } static void LockTooMuch(object lock1, object lock2) { lock (lock1) { Thread.Sleep(1000); lock (lock2) ; } } 运行结果如下图所示因为使用Monitor.TryEnter()方法在超时以后会返回不会阻塞线程所以没有发生死锁。而第二段代码中lock没有超时返回的功能导致资源死锁同步块中的代码永远不会被执行。 1.12 多线程中处理异常# 在多线程中处理异常应当使用就近原则在哪个线程发生异常那么所在的代码块一定要有相应的异常处理。否则可能会导致程序崩溃、数据丢失。 主线程中使用try/catch语句是不能捕获创建线程中的异常。但是万一遇到不可预料的异常可通过监听AppDomain.CurrentDomain.UnhandledException事件来进行捕获和异常处理。 演示代码如下所示异常处理 1 和 异常处理 2 能正常被执行而异常处理 3 是无效的。 Copy static void Main(string[] args) { // 启动线程线程代码中进行异常处理 var t new Thread(FaultyThread); t.Start(); t.Join(); // 捕获全局异常 AppDomain.CurrentDomain.UnhandledException CurrentDomain_UnhandledException; t new Thread(BadFaultyThread); t.Start(); t.Join(); // 线程代码中不进行异常处理尝试在主线程中捕获 AppDomain.CurrentDomain.UnhandledException - CurrentDomain_UnhandledException; try { t new Thread(BadFaultyThread); t.Start(); } catch (Exception ex) { // 永远不会运行 Console.WriteLine($异常处理 3 : {ex.Message}); } } private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Console.WriteLine($异常处理 2 {(e.ExceptionObject as Exception).Message}); } static void BadFaultyThread() { Console.WriteLine(有异常的线程已启动...); Thread.Sleep(TimeSpan.FromSeconds(2)); throw new Exception(Boom!); } static void FaultyThread() { try { Console.WriteLine(有异常的线程已启动...); Thread.Sleep(TimeSpan.FromSeconds(1)); throw new Exception(Boom!); } catch (Exception ex) { Console.WriteLine($异常处理 1 : {ex.Message}); } } 运行结果如下图所示与预期结果一致。 参考书籍 本文主要参考了以下几本书在此对这些作者表示由衷的感谢你们提供了这么好的资料。 《CLR via C#》《C# in Depth Third Edition》《Essential C# 6.0》《Multithreading with C# Cookbook Second Edition》线程基础这一章节终于整理完了是笔者学习过程中的笔记和思考。计划按照《Multithreading with C# Cookbook Second Edition》这本书的结构一共更新十二个章节先立个Flag。 源码下载点击链接 示例源码下载 笔者水平有限如果错误欢迎各位批评指正
http://wiki.neutronadmin.com/news/133504/

相关文章:

  • 做设计的都用那些网站邯郸百姓网免费发布信息
  • 六安哪里有做推广网站商城开发网站
  • 建立网站有什么要求电商怎么注册开店
  • 西安便宜做网站的高端网站建设报价
  • 万脑网站建设工程管理咨询公司
  • 自己个人网站后台怎么做有手机版本wordpress
  • 效果图参考网站贴wordpress插件
  • 网站开发 安全验证门户网站demo下载
  • 唐山专业做网站系统优化的方法知识点
  • 佛山市网站建设系统百度站长工具排名
  • 安徽鑫华建设有限公司网站关于建设网站的合作合同范本
  • 用vue做的网站怎么实现响应式代理行业门户网站
  • 网站建设 php 企业网站网建类公司
  • 红酒哪个网站做的好保网官网
  • 三水网站制作公司网站建设英文翻译
  • 都江堰做网站创建公司需要什么
  • 网站做调查问卷给钱的兼职昆明长尾词seo怎么优化
  • 做网站公司宣传语的网站建设公司
  • 义乌市评建设职称网站交易网站制度建设
  • 滕州市东方建设工程事务有限公司网站凡科互动答题辅助
  • 阿里 网站建设投资融资理财网站模板
  • 做网站收费 优帮云pdf 网站建设
  • 深圳外贸网站推广惠州外贸网站建设推广
  • 制作论文招聘网站的班级优化大师app下载学生版
  • 番禺做网站设计wordpress菜单设置图标
  • 太原市建设工程质量监督站网站保洁公司 网站模板
  • 成都网站设计师做网站建设的销售薪水
  • 淄博网站建设企业千锋教育怎么样
  • pycharm做网站群晖wordpress建站教程
  • 上海网站建设目的年报申报入口官网